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  
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 			// handle the Android Notification
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 			// ignore
156 		}
157 
158 		public void randomChanged(Random random) {
159 			// ignore
160 		}
161 
162 		public void repeatChanged(Repeat repeat) {
163 			// ignore
164 		}
165 
166 		public void trackChanged(PlaylistItem track, boolean play,
167 				int positionMillis) {
168 			// update the Android Notification
169 			final boolean isPlaying = AmbientApplication.getInstance()
170 					.getPlaylist().getPlaybackState() == PlayerStateEnum.Playing;
171 			if ((track != null) && isPlaying) {
172 				createNotification(track.getTrack());
173 			}
174 			// notify the user with the Toast
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 			// ignore
190 		}
191 
192 		public void buffered(byte percent) {
193 			// ignore
194 		}
195 
196 		public void radioNewTrack(String name) {
197 			// update the Android Notification
198 			final TrackMetadataBean.Builder b = new TrackMetadataBean.Builder();
199 			createNotification(b.setOrigin(TrackOriginEnum.Shoutcast)
200 					.setLocation("Shoutcast radio").setTitle(name).build(-1));
201 			// notify the user with the Toast
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 			// ignore this low-level event, we are primarily capturing
211 			// higher-level
212 			// events
213 		}
214 
215 		public void stopped(String error, boolean errorMissing,
216 				TrackOriginEnum origin) {
217 			// ignore this low-level event, we are primarily capturing
218 			// higher-level
219 			// events
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 		// format the notification message
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 		// notify
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 }