1 /*
  2  * 	Copyright (C) 2012-2013 DFKI GmbH
  3  * 	Deutsches Forschungszentrum fuer Kuenstliche Intelligenz
  4  * 	German Research Center for Artificial Intelligence
  5  * 	http://www.dfki.de
  6  * 
  7  * 	Permission is hereby granted, free of charge, to any person obtaining a 
  8  * 	copy of this software and associated documentation files (the 
  9  * 	"Software"), to deal in the Software without restriction, including 
 10  * 	without limitation the rights to use, copy, modify, merge, publish, 
 11  * 	distribute, sublicense, and/or sell copies of the Software, and to 
 12  * 	permit persons to whom the Software is furnished to do so, subject to 
 13  * 	the following conditions:
 14  * 
 15  * 	The above copyright notice and this permission notice shall be included 
 16  * 	in all copies or substantial portions of the Software.
 17  * 
 18  * 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 19  * 	OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 20  * 	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 21  * 	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
 22  * 	CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
 23  * 	TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 24  * 	SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 25  */
 26 
 27 
 28 define(['module', 'constants', 'mediaManager', 'dictionary'], 
 29 	/**
 30 	 * 
 31 	 * @name NotificationManager
 32 	 * @memberOf mmir
 33 	 * @static
 34 	 * @class
 35 	 * 
 36 	 * @requires Dictionary
 37 	 * @requires mmir.MediaManager
 38 	 */
 39 	function(
 40 		module, constants, mediaManager, Dictionary
 41 ){
 42 	//the next comment enables JSDoc2 to map all functions etc. to the correct class description
 43 	/** @scope mmir.NotificationManager.prototype *///for jsdoc2
 44 	
 45 	
 46     //private members
 47 	
 48 	
 49 	/**
 50 	 * TODO replace by "real" ENV mechanism ... instead of !forBrowser / ! constants.isBrowserEnv()
 51 	 * 
 52 	 * @private
 53 	 * @memberOf NotificationManager#
 54 	 */
 55 	var isCordovaEnv = ! constants.isBrowserEnv();
 56 	
 57 	/**
 58 	 * @private
 59 	 * @memberOf NotificationManager#
 60 	 */
 61     var instance = null;
 62     
 63     
 64     //private methods
 65     
 66     /**
 67 	 * Constructor-Method of Singleton mmir.NotificationManager.<br> 
 68 	 * 
 69 	 * @constructs NotificationManager
 70 	 * @memberOf NotificationManager#
 71 	 * @ignore
 72 	 */
 73     function constructor(){
 74     	
 75     	/**
 76     	 * @private
 77     	 * @memberOf NotificationManager.prototype
 78     	 */
 79     	var INIT = 'init';
 80     	
 81     	/**
 82     	 * VIBRATE initialization status
 83     	 * @private
 84     	 * @memberOf NotificationManager.prototype
 85     	 */
 86     	var isHapticEnabled = true;
 87 
 88     	/**
 89     	 * Implementation for vibrate-function:
 90     	 * platform-dependent (if platform/device does not support it: as stub-function)
 91     	 * 
 92     	 * @private
 93     	 * @type {Function}
 94     	 * @memberOf NotificationManager.prototype
 95     	 */
 96     	var doVibrate = null;
 97     	
 98     	/**
 99     	 * Implementation for confirm-function:
100     	 * shows "native" platform-specific confirm-dialog.
101     	 * 
102     	 * <code>function(message, confirmCallback, title, buttonLabels)</code>
103     	 * 
104     	 * @private
105     	 * @type {Function}
106     	 * @memberOf NotificationManager.prototype
107     	 */
108     	var doConfirm = null;
109     	
110     	/**
111     	 * Implementation for confirm-function:
112     	 * shows "native" platform-specific alert-dialog.
113     	 * 
114     	 * <code>(message, alertCallback, title, buttonName)</code>
115     	 * 
116     	 * @private
117     	 * @type {Function}
118     	 * @memberOf NotificationManager.prototype
119     	 */
120     	var doAlert = null;
121     	
122     	/**
123     	 * Initialize the NotificationManager.
124     	 * 
125     	 * At the moment this set the internal vibrate-function,
126     	 * if available in the current execution environment
127     	 * (or with a dummy function, if not).
128     	 * 
129     	 * In addition, the alert-, and confirm-functions are set to their
130     	 * platform-specific implementation.
131     	 * 
132     	 * @memberOf NotificationManager.prototype
133     	 * @private
134     	 * @function
135     	 */
136     	var _init = function(){
137 	    	if(isCordovaEnv){
138 	    		
139 	    		if(navigator.notification && navigator.notification.vibrate){
140 //		    		console.debug('Vibrate: navigator.notification');
141 	    			/** @ignore */
142 		    		doVibrate = function vibrate(n){ navigator.notification.vibrate(n); };
143 	    		}
144 	    		else {
145 	    			console.warn('mmir.NotificationManager.INIT: could not detect navigator.notification.vibrate, using NOOP dummy instead.');
146 	    			/** @ignore */
147 	        		doVibrate = function dummyVibrate(n){ console.error('mmir.NotificationManager.vibrate('+n+') triggered in CORDOVA environment, but no VIBRATE functionality available.'); };// DEBUG
148 	    		}
149 	    		
150 	    	}
151 	    	else if (navigator.vibrate){
152 //	    		console.debug('Vibrate API');
153 	    		/** @ignore */
154 	    		doVibrate = function vibrate(n){ navigator.vibrate(n); };
155 	    	}
156 	    	else if (navigator.webkitVibrate){
157 //	    		console.debug('Vibrate: webkit');
158 	    		/** @ignore */
159 	    		doVibrate = function vibrate(n){ navigator.webkitVibrate(n); };
160 	    	}
161 	    	
162 	    	//set confirm-implementation
163 	    	if(navigator.notification && navigator.notification.confirm){
164 //	    		console.debug('Confirm: navigator.notification');
165     			/** @ignore */
166 	    		doConfirm = function confirm(message, confirmCallback, title, buttonLabels){
167 	    			
168 	    			var cbWrapper = confirmCallback;
169 	    			if(confirmCallback){
170 	    				var self = this;
171 		    			cbWrapper = function(result){
172 		    				//need to convert NUMBER result to BOOLEAN:
173 		    				//  result = [1,2,..] 
174 		    				//  -> default is: OK = 1, CANCEL = 2, close-the-dialog = 0
175 		    				var res = result === 1 ? true : false;
176 		    				confirmCallback.call(self, res);
177 		    			};
178 	    			}
179 	    			
180 	    			navigator.notification.confirm(message, cbWrapper, title, buttonLabels);
181 	    		};
182     		}
183     		else if(typeof window !== 'undefined' && window && window.confirm) {
184     			/** @ignore */
185     			doConfirm = function confirmWindow(message, confirmCallback, title, buttonLabels){
186     				//TODO use setTimeout here to "simulate" async execution?
187     				var result = window.confirm(message);
188     				if(confirmCallback){
189     					confirmCallback.call(this, result);
190     				}
191     			};
192     		}
193 	    	
194 	    	//set alert-implementation
195 	    	if(navigator.notification && navigator.notification.alert){
196 //	    		console.debug('Alert: navigator.notification');
197     			/** @ignore */
198 	    		doAlert = function confirm(message, alertCallback, title, buttonLabels){
199 	    			navigator.notification.alert(message, alertCallback, title, buttonLabels);
200 	    		};
201     		}
202     		else if(typeof window !== 'undefined' && window && window.alert){
203     			/** @ignore */
204     			doAlert = function confirmWindow(message, alertCallback, title, buttonLabels){
205     				//TODO use setTimeout here to "simulate" async execution?
206     				window.alert(message);
207     				if(alertCallback){
208     					alertCallback.call(this);
209     				}
210     			};
211     		}
212     	};
213     	
214     	
215     	//SOUND / BEEP initialization:
216     	
217     	/**
218     	 * @private
219     	 * @type Number
220     	 * 
221     	 * @memberOf NotificationManager.prototype
222     	 */
223     	var beepVolume = 1.0;
224     	
225     	/**
226     	 * The Audio object for the <em>beep</em> sound.
227     	 * 
228     	 * @private
229     	 * @type AudioObject
230     	 * 
231     	 * @memberOf NotificationManager.prototype
232     	 */
233     	var beepAudio = null;
234     	
235     	/**
236     	 * Dictionary that manages the currently loaded sounds
237     	 * 
238     	 * @private
239     	 * @type Dictionary
240     	 * 
241     	 * @memberOf NotificationManager.prototype
242     	 */
243     	//TODO add option for limiting size of soundMap (-> e.g. how many resources are max. cached/occupied for Android) 
244     	var soundMap = new Dictionary();
245     	
246     	/**
247     	 * Factory function for creating "sounds objects",
248     	 * i.e. extend the basic Audio objects with needed functions/properties
249     	 * 
250     	 * @private
251     	 * @function
252     	 * 
253     	 * @param {mmir.env.media.IAudio} audioObj
254     	 * @param {String} name
255     	 * 
256     	 * @returns {mmir.env.media.INotificationSound} the extended audio object, i.e. a NotificationSound
257     	 * 
258     	 * @memberOf NotificationManager.prototype
259     	 */
260     	function initNotificationSound(audioObj, name){
261     		audioObj.name = name;
262     		audioObj.setVolume(beepVolume);
263 			audioObj.isNotificationPlaying = false;
264 			audioObj.repeatNotification = 0;
265 			audioObj.playNotification = function(repeatNTimes){
266 				
267 //				console.debug('isPlaying: '+this.isNotificationPlaying+', repeat: '+this.repeatNotification+',  args: '+repeatNTimes+'');
268 				
269 				//"blocking mode": only re-start, if not already playing
270 				if(!this.isNotificationPlaying){
271 					this.repeatNotification = repeatNTimes ? repeatNTimes : 0;
272 				}
273 				
274 				if( this.repeatNotification < 1){
275 					//end recurusion
276 					this.isNotificationPlaying = false;
277 					this.repeatNotification = 0;
278 				}
279 				else {
280 					this.isNotificationPlaying = true;
281 					--this.repeatNotification;
282 //					this.stop();
283 					this.play();
284 				}
285 			};
286 			
287 			audioObj.setCallbacks = function(onFinished, onError){
288 				this.onFinishedListener = onFinished;
289 				this.onErrorListener = onError;
290 			};
291 			audioObj.clearCallbacks = function(){
292 				this.onFinishedListener = null;
293 				this.onErrorListener = null;
294 			};
295 			audioObj.fireFinished = function(){
296 				
297 				var tempOnFinishedListener = this.onFinishedListener;
298 				//clear callbacks
299 				// NOTE: do this before triggering callback, in case the callback re-plays the notification with new callbacks!
300 				//       (if we would clear callbacks after invocation, we would delete the new callbacks!)
301 				this.clearCallbacks();
302 				if(tempOnFinishedListener){
303 					tempOnFinishedListener();
304 				}
305 			};
306 			audioObj.fireError = function(error){
307 				
308 				var tempOnErrorListener = this.onErrorListener;
309 				//clear callbacks
310 				// NOTE: do this before triggering callback, in case the callback re-plays the notification with new callbacks!
311 				//       (if we would clear callbacks after invocation, we would delete the new callbacks!)
312 				this.clearCallbacks();
313 				
314 				//create error message with details
315 				var id;
316 				if(this.name){
317 					var entry = soundMap.get(this.name);
318 					id = '"' + this.name + '" -> ' + (entry? '"'+entry.url+'"' : 'UNDEF'); 
319 				}
320 				else {
321 					id = '"BEEP" -> "'+constants.getBeepUrl()+'"'; 
322 				}
323 				var msg = 'Notification: Error playing the sound for notification '+id;
324 				
325 				//create Error object if necessary
326 				if(!error){
327 					error = new Error(msg);
328 					msg = null;
329 				}
330 				
331 				
332 				if(tempOnErrorListener){
333 					tempOnErrorListener(error, msg);
334 				}
335 				else {
336 					//if no callback: print debug output in error stream:
337 					console.error( (msg? msg + ': ' : '') + error);
338 				}
339 			};
340 			
341 			return audioObj;
342     	};
343     	
344     	/**
345     	 * Helper for creating an Audio object
346     	 * 
347     	 * @private
348     	 * @function
349     	 * 
350     	 * @param {String} url
351     	 * @param {Function} success
352     	 * @param {Function} fail
353     	 * @param {Function} init
354     	 * 
355     	 * @returns {AudioObject} audio object
356     	 * 
357     	 * @memberOf NotificationManager.prototype
358     	 */
359     	function createAudio(url, success, fail, init){
360     		return mediaManager.getURLAsAudio(url, success, fail, init);
361     	}
362     	
363     	/**
364     	 * Helper for "registering" a NotificationSound.
365     	 * 
366     	 * Stores the sound object in {@link #soundMap}
367     	 * with the ID <code>name</code>.
368     	 * 
369     	 * The sound object will be initialized on first
370     	 * retrieval, ie. {@link #doGetSoundFromMap}
371     	 * 
372     	 * @private
373     	 * @function
374     	 * 
375     	 * @param {String} name
376     	 * @param {String} theUrl
377     	 * @param {Boolean} isKeepOnPause
378     	 * 
379     	 * @memberOf NotificationManager.prototype
380     	 */
381     	function initAudioSoundEntry(name, theUrl, isKeepOnPause){
382     		var config = {url: theUrl, audio: null};
383     		if(isKeepOnPause){
384     			config.isKeepOnPause = true;
385     		}
386     		soundMap.put(name, config);
387     	}
388     	
389     	/**
390     	 * Helper for retrieving an existing sound from
391     	 * the {@link #soundMap}.
392     	 * 
393     	 * Initializes the sound if necessary.
394     	 * 
395     	 * @private
396     	 * @function
397     	 * 
398     	 * @param {String} name
399     	 * @param {Function} onErrorCallback
400     	 * 
401     	 * @memberOf NotificationManager.prototype
402     	 */
403     	function doGetSoundFromMap(name, onErrorCallback){
404     		var audioObj = null;
405     		var audioUrl = null;
406     		var keepOnPause = false;
407     		
408     		//if no name: use default beep
409     		if(!name){
410     			audioObj = beepAudio;
411     			audioUrl = constants.getBeepUrl();
412     		}
413     		else {
414     			//retrieve information for sound
415     			var soundEntry = soundMap.get(name);
416     			if(soundEntry === INIT){
417     				//TODO set repeat-times?
418     				
419     				//sound is still initializing -> return
420     				return null; ////////////////////// EARLY EXIT //////////////////////
421     			}
422     			
423     			if(!soundEntry){
424     				var errMsg = 'mmir.NotificationManager: no sound "'+name+'" initialized!';
425     				if(onErrorCallback){
426     					onErrorCallback(errMsg);
427     				}
428     				else {
429     					console.error(errMsg);
430     				}
431 //    				throw new Error(errMsg);
432     				return null; ////////////////////// EARLY EXIT //////////////////////
433     			}
434     			
435     			audioObj = soundEntry.audio;//<- may be null
436     			audioUrl = soundEntry.url;//<- must NOT be null
437     			keepOnPause = soundEntry.isKeepOnPause? true : false;
438     		}
439     		
440     		return {
441     			sound: audioObj,
442     			url: audioUrl,
443     			isKeepOnPause: keepOnPause
444     		};
445     	}
446     	
447     	/**
448     	 * Helper for playing a registered notification sound.
449     	 * 
450     	 * Initializes the sound if necessary.
451     	 * 
452     	 * @private
453     	 * @function
454     	 * 
455     	 * @param {String} name
456     	 * 				ID of the sound
457     	 * @param {Number} times 
458     	 * @param {Function} onFinishedCallback
459     	 * @param {Function} onErrorCallback
460     	 * 
461     	 * @memberOf NotificationManager.prototype
462     	 */
463     	function playAudioSound(name, times, onFinishedCallback, onErrorCallback){
464     		
465     		var soundEntry = doGetSoundFromMap(name, onErrorCallback);
466     		
467     		if(soundEntry === null){
468     			//could not retrieve sound-object
469     			// (error callback will already have been invoked, so just return)
470     			return;/////////////////////// EARYL EXIT ///////////
471     		}
472     		
473     		var audioObj = soundEntry.sound;
474     		var audioUrl = soundEntry.url;
475     		var isKeepOnPause = soundEntry.isKeepOnPause;
476 
477     		//create audio-object, if not existing yet
478     		if(audioObj === null){
479     			
480     			if(name){
481     				soundMap.put(name, INIT);
482     			}
483 
484     			audioObj = createAudio(
485     					audioUrl,
486     					function onFinished(){ 
487     						this.playNotification();
488 
489     						audioObj.fireFinished();
490     					}, 
491     					function onError(e){ 
492     						if(name) {
493     							soundMap.remove(name);
494     						};
495     						
496     						if(audioObj){
497     							audioObj.fireError(e);
498     						}
499     						else {
500     							if(onErrorCallback){
501     								onErrorCallback();
502     							}
503     							else {
504     								console.error('Notification: Error playing the sound from "'+audioUrl+'": '+e);
505     							}
506     						}
507     					},
508     					function onInit(){ 
509     						
510     						//FIX for Android/Cordova: return-value of createAudio will not set audioObj "fast enough"
511     						// 						   (i.e. may not use async-initialization, depending on where the audio-file is located)
512     						//						   ... in order to be able to use keep variable audioObj useful -> do assignment now/here
513     						audioObj = this;
514 
515     	    				initNotificationSound(audioObj, name);
516     	    				audioObj.setCallbacks(onFinishedCallback, onErrorCallback);
517     	    				
518     	    	    		//if no name: assume default beep
519     	    				if(!name){
520     	    					beepAudio = audioObj;
521     	    	    		}
522     	    				else {
523     	    					var theEntry = {url: audioUrl, audio: audioObj};
524     	    					if(isKeepOnPause){
525     	    						theEntry.isKeepOnPause = true;
526     	    					}
527     	    					soundMap.put(name, theEntry);
528     	    				}
529     	    				
530     						audioObj.playNotification(times);
531     	    				
532     	    			}
533     			);
534     			
535 //    			//FIXME this is a QUICK-FIX:
536 //    			//		Android needs invocation of a media-method, before it triggers the on-init callback.
537 //    			//		We need to do this here, not within the
538 //    			if(isCordovaEnv){
539 //    				audioObj.init();
540 //    			}
541     		}
542     		else {
543 				audioObj.setCallbacks(onFinishedCallback, onErrorCallback);
544     			audioObj.playNotification(times);
545     		}
546     		
547     	}
548     	
549     	/**
550     	 * Helper for stop playing a registered notification sound.
551     	 * 
552     	 * Initializes the sound if necessary.
553     	 * 
554     	 * @private
555     	 * @function
556     	 * 
557     	 * @param {String} name
558     	 * 				ID of the sound
559     	 * @param {Function} onFinishedCallback
560     	 * @param {Function} onErrorCallback
561     	 * 
562     	 * @memberOf NotificationManager.prototype
563     	 */
564     	function stopAudioSound(name, onFinishedCallback, onErrorCallback){
565     		
566     		var soundEntry = doGetSoundFromMap(name, onErrorCallback);
567     		
568 
569 //			console.error('Notification: invoked stop on notification-sound '+name+' -> '+JSON.stringify(soundEntry));//FIXM debug
570     		
571     		if(soundEntry === null){
572     			//could not retrieve sound-object
573     			// (error callback will already have been invoked, so just return)
574     			return;/////////////////////// EARYL EXIT ///////////
575     		}
576     		
577     		var audioObj = soundEntry.sound;
578     		//NOTE audioObj may be null, e.g. if sound is still initializing.
579     		
580     		if(audioObj != null){
581     			if(audioObj.repeatNotification > 0)
582     				audioObj.repeatNotification = 0;
583     			if(audioObj.isNotificationPlaying === true)
584     				audioObj.isNotificationPlaying = false;
585     			if(audioObj.stop){
586 //  				console.error('Notification: stopping notification-sound -> '+name);//FIXM debug
587     				audioObj.stop();
588     			}
589     		}
590     		
591     		if(onFinishedCallback){
592     			onFinishedCallback.call(audioObj);
593     		}
594     		
595     	};
596     	
597     	//on Android: release resources on pause/exit, since they are limited
598     	if(isCordovaEnv){
599     		
600     		document.addEventListener("resume", function(event){
601     			//initialize beep sound:
602         		playAudioSound(null, 0);
603     		});
604     		
605     		document.addEventListener(
606     				"pause", 
607     				function(event){
608     					
609     					//use temporal variable for minimizing concurrency problems
610     					var temp;
611     					
612     					if(beepAudio !== null){
613     						
614     						temp = beepAudio;
615     						beepAudio = null;
616     						temp.release();
617     						
618     						console.info('Notification: released media resources for beep.');
619     					}
620     					
621     					var keys = soundMap.getKeys();
622     					for(var i = keys.length - 1; i >= 0; --i){
623     						
624     						var entry = soundMap.get(keys[i]);
625 	    					if(entry !== null && entry != INIT && ! entry.isKeepOnPause){
626 	    						
627 	    						temp = entry.audio;
628 	    						
629 	    						//audio may not be initialized yet:
630 	    						if(temp != null){
631 		    						entry.audio = null;
632 		    						temp.release();
633 	    						}
634 	    						
635 	    						console.info('Notification: released media resources for '+entry.url);
636 	    					}
637     					}
638     				},
639     				false
640     		);
641     	}
642     	
643 		/** @lends mmir.NotificationManager.prototype */
644     	return { //public members and methods
645     		/** @scope mmir.NotificationManager.prototype */
646             
647         	/**
648         	 * Trigger a haptic vibration feedback.
649         	 * 
650         	 * <p>Note: The device / execution environment may not support haptic vibration feedback
651         	 * 
652         	 * @function
653         	 * @param {Number} milliseconds
654         	 * 		duration for vibration in milliseconds. Must be <code>> 0</code>
655         	 * @public
656         	 * 
657         	 * @memberOf mmir.NotificationManager.prototype
658         	 */
659             vibrate: function(milliseconds){
660             	if (isHapticEnabled && doVibrate){
661             		doVibrate(milliseconds);
662             	}
663             },
664             /**
665              * Check if {@link #vibrate} is functional and enabled.
666              * 
667              * <p>
668              * If <code>false</code> is returned, calling the <code>vibrate()</code>
669              * function will have no effect.
670              * 
671         	 * @function
672         	 * @returns {Boolean} <code>true</code> if {@link #vibrate} is functional
673         	 * @public
674         	 */
675             isVibrateEnabled: function(){
676             	if (isHapticEnabled && doVibrate){
677             		return true;
678             	}
679             	else {
680             		return false;
681             	}
682             },
683             /**
684              * Check if the execution environment supports {@link #vibrate}.
685              * 
686              * <p>
687              * If <code>false</code> is returned, calling the <code>vibrate()</code>
688              * function will have no effect.
689              * 
690         	 * @function
691         	 * @returns {Boolean} <code>true</code> if {@link #vibrate} is functional
692         	 * @public
693         	 */
694             isVibrateAvailable: function(){
695             	if (doVibrate){
696             		return true;
697             	}
698             	else {
699             		return false;
700             	}
701             },
702             /**
703              * Enable or disable {@link #vibrate}.
704              * <p>
705              * NOTE: If {@ #isVibrateAvailable} returns <code>false</code>, enabling will have no effect.
706              * 
707         	 * @function
708         	 * @public
709         	 * 
710         	 * @param {Boolean} enabled
711         	 * 			set vibrate function to <code>enable</code>
712         	 */
713             setVibrateEnabled: function(enabled){
714             	isHapticEnabled = enabled;
715             },
716             /**
717              * Opens a (native) alert-notification dialog.
718              * 
719              * @param {String} message
720              * 				the alert message
721              * @param {Function} [alertCallback]			
722              * 				callback that is triggered, after dialog was closed
723              * @param {String} [title] OPTIONAL
724              * 				the title for the alert dialog
725              * 				(may not be provided / settable in all execution environments)
726              * @param {String} [buttonName] OPTIONAL
727              * 				the label for the close button in the alert dialog
728              * 				(may not be provided / settable in all execution environments)
729              * @function
730              * @public
731              */
732             alert: function(message, alertCallback, title, buttonName){
733             	if(doAlert){
734             		doAlert.call(this, message, alertCallback, title, buttonName);
735             	}
736             	else {
737             		console.warn('NotificationManager.alert: No alert dialog implementation available ', message, alertCallback, title, buttonName);
738             	}
739             },
740             /**
741              * Opens a (native) confirm-notification dialog.
742              * 
743              * @param {String} message
744              * 				the confirm message
745              * @param {Function} [alertCallback]			
746              * 				callback that is triggered, after dialog was closed.
747              * 				The callback will be invoked with 1 argument:<br>
748              * 				<code>callback(wasConfirmed : Boolean)</code><br>
749              * 				if the OK/CONFIRM button was pressed, <code>wasConfirmed</code>
750              * 				will be <code>true</code>, otherwise <code>false</code>.
751              * @param {String} [title] OPTIONAL
752              * 				the title for the confirm dialog
753              * 				(may not be provided / settable in all execution environments)
754              * @param {Array<String>} [buttonLabels] OPTIONAL
755              * 				the labels for the buttons of the confirm dialog
756              * 				(may not be provided / settable in all execution environments)
757              * 
758              * @function
759              * @public
760              */
761             confirm: function(message, confirmCallback, title, buttonLabels){
762             	if(doConfirm){
763             		doConfirm.call(this, message, confirmCallback, title, buttonLabels);
764             	}
765             	else {
766             		console.warn('NotificationManager.confirm: No confirm dialog implementation available ', message, confirmCallback, title, buttonLabels);
767             	}
768             },
769             /**
770              * Trigger a beep notification sound.
771              * 
772              * @function
773              * @param {Number} times
774              * 			how many times should to beep repeated
775              * @public
776              */
777             beep: function(times){
778         		if (times>0){
779         			playAudioSound(null, times);
780         		}
781             },
782             
783             getVolume: function(){
784             	return beepVolume;
785             },
786             /**
787              * Set the volume for sound notifications.
788              * 
789              * @param {Number} vol
790              * 			the new volume: a number between [0, 1]
791              * 
792              * @see mmir.env.media.IAudio#setVolume
793              */
794             setVolume: function(vol){
795             	if(typeof vol !== 'number'){
796             		throw new TypeError('argument vol (value: '+vol+') must be a number, but is instead: '+(typeof vol));
797             	}
798             	
799             	if(vol !== beepVolume){
800             		
801             		//set volume for beep notification
802 	            	beepVolume = vol;
803 	            	if(beepAudio){
804 	            		beepAudio.setVolume(beepVolume);
805 	            	}
806 	            	
807 	            	//set volume for notification sounds
808 	            	var keys = soundMap.getKeys();
809 	            	var entry = null;
810 	            	for(var i = 0, size = keys.length; i < size; ++i){
811 	            		entry = soundMap.get(keys[i]);
812 	            		if(entry.audio !== null){
813 	            			entry.audio.setVolume(vol);
814 	            		}
815 	            	}
816 	            	
817             	}
818             }
819             
820             /**
821              * Trigger a sound notification by NAME (needs to be created first).
822              * 
823              * @function
824              * @param {String} name
825              * 				the name / identifier for the sound (if <code>null</code>, beep notification is used)
826              * @param {Number} times
827              * 				how many times should to beep repeated
828              * @public
829              * 
830              * @see #createSound
831              */
832             ,playSound: function(name, times, onFinished, onError){
833         		if (times>0){
834         			playAudioSound(name, times, onFinished, onError);
835         		}
836             },
837             /**
838              * Create a sound notification.
839              * 
840              * <p>
841              * After creation, the sound "theSoundId" can be played via 
842              * <code>playSound("theSoundId", 1)</code>
843              * 
844              * @function
845              * @param {String} name 
846              * 			the name / identifier for the sound
847              * @param {String} url
848              * 			the URL for the audio of the sound
849              * @param {Boolean} [isKeepOnPause] OPTIONAL
850              * 			flag indicating, if the audio resources should be keept
851              * 			when the device goes into <em>pause mode</em>
852              * 			(may not apply to all execution environments; 
853              * 			 e.g. relevant for Android environment)
854              * 			<br>
855              * 			DEFAULT: <code>false</code> 
856              * @public
857              */
858             createSound: function(name, url, isKeepOnPause){ // TODO add callbacks? this would make the impl. more complex ..., successCallback, errorCallback){
859             	initAudioSoundEntry(name, url, isKeepOnPause);
860 
861             	//DISABLED this triggers an error if MediaManager / LanguageManager etc. are not initialized yet!
862 //            	console.error('created sound "'+name+'" for url "'+url+'", calling from: ' + new Error().stack);
863 //            	//immediately initialize the sound (but do not play it yet);
864 //            	playAudioSound(name, 0);
865             }
866             /**
867              * Stop a sound notification, if it is playing.
868              * 
869              * Has no effect, if the notification is not playing.
870              * 
871              * @function
872              * @param {String} name 
873              * 			the name / identifier for the sound
874              */
875             ,stopSound: function(name){
876         		stopAudioSound(name);
877             }
878             
879             , initBeep: function(){//<- used by framework to initialize the default beep-sound
880             	//initialize beep sound:
881         		playAudioSound(null, 0);
882             }
883             /**
884              * Initialize a sound notification.
885              * 
886              * <p>
887              * NOTE a sound does not need to be explicitly initialized, <code>playSound</code> will
888              * automatically initialize the sound if necessary.
889              * 
890              * <p>
891              * Initializing a sound prepares all resources, so that the sound can be immediately played.
892              * 
893              * For instance, a sound that needs to loaded from a remote server first, may take some time
894              * before it can be played.
895              * 
896              * <p>
897              * NOTE the sound must be {@link #createSound|created} first, before initializing it.
898              * 
899              * @function
900              * @param {String} name 
901              * 			the name / identifier for the sound
902              * @public
903              * 
904              * @see #createSound
905              */
906             , initSound: function(name){
907             	//initialize sound (identified by its name):
908         		playAudioSound(name, 0);
909             }
910             , init: function(){//<- used by framework to initialize the NotificationManager
911             	_init();
912             	this.init = function(){ return this; };
913             	
914             	return this;
915             }
916         };
917     }
918     
919     instance = new constructor();
920     	    
921 	/**
922 	 * @deprecated instead: use mmir.NotificationManager directly
923 	 * 
924 	 * @function
925 	 * @name getInstance
926 	 * @memberOf mmir.NotificationManager#
927 	 */
928 	instance.getInstance = function(){
929 		return instance;
930 	};
931     		
932     return instance;
933     
934 });//END: define(..., function(){...