feature (GH-128) ios/android: enable programmatic dismissal of open dialogs
diff --git a/README.md b/README.md
index 482b16d..d7156c7 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,8 @@
 - `navigator.notification.confirm`
 - `navigator.notification.prompt`
 - `navigator.notification.beep`
+- `navigator.notification.dismissPrevious`
+- `navigator.notification.dismissAll`
 
 ## navigator.notification.alert
 
@@ -223,3 +225,63 @@
 ### Android Quirks
 
 - Android plays the default __Notification ringtone__ specified under the __Settings/Sound & Display__ panel.
+
+## navigator.notification.dismissPrevious
+
+Dismisses the previously opened dialog box.
+If no dialog box is currently open, the `errorCallback` will be called.
+
+    navigator.notification.dismissPrevious([successCallback], [errorCallback])
+
+- __successCallback__: Callback to invoke when previously opened dialog has been dismissed. _(Function)_ (Optional)
+- __errorCallback__: Callback to invoke on failure to dismiss previously opened dialog. Will be passed the error message. _(Function)_ (Optional)
+
+### Example
+
+    function successCallback() {
+        console.log("Successfully dismissed previously opened dialog.");
+    }
+    
+    function errorCallback(error) {
+        console.log("Failed to dismiss previously opened dialog: " + error);
+    }
+
+    navigator.notification.dismissPrevious(
+        successCallback,
+        errorCallback
+    );
+
+### Supported Platforms
+
+- Android
+- iOS
+
+## navigator.notification.dismissAll
+
+Dismisses all previously opened dialog boxes.
+If no dialog box is currently open, the `errorCallback` will be called.
+
+    navigator.notification.dismissAll([successCallback], [errorCallback])
+
+- __successCallback__: Callback to invoke when all previously opened dialogs have been dismissed. _(Function)_ (Optional)
+- __errorCallback__: Callback to invoke on failure to dismiss all previously opened dialogs. Will be passed the error message. _(Function)_ (Optional)
+
+### Example
+
+    function successCallback() {
+        console.log("Successfully dismissed all previously opened dialogs.");
+    }
+    
+    function errorCallback(error) {
+        console.log("Failed to dismiss all previously opened dialogs: " + error);
+    }
+
+    navigator.notification.dismissAll(
+        successCallback,
+        errorCallback
+    );
+
+### Supported Platforms
+
+- Android
+- iOS
diff --git a/src/android/Notification.java b/src/android/Notification.java
index 8e34e54..5a0f83b 100644
--- a/src/android/Notification.java
+++ b/src/android/Notification.java
@@ -38,6 +38,7 @@
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
+import java.util.ArrayList;
 
 
 /**
@@ -61,10 +62,14 @@
     private static final String ACTION_PROGRESS_START = "progressStart";
     private static final String ACTION_PROGRESS_VALUE = "progressValue";
     private static final String ACTION_PROGRESS_STOP  = "progressStop";
+    private static final String ACTION_DISMISS_PREVIOUS  = "dismissPrevious";
+    private static final String ACTION_DISMISS_ALL  = "dismissAll";
 
     private static final long BEEP_TIMEOUT   = 5000;
     private static final long BEEP_WAIT_TINE = 100;
 
+    private ArrayList<AlertDialog> dialogs = new ArrayList<AlertDialog>();
+
     public int confirmResult = -1;
     public ProgressDialog spinnerDialog = null;
     public ProgressDialog progressDialog = null;
@@ -122,6 +127,12 @@
         else if (action.equals(ACTION_PROGRESS_STOP)) {
             this.progressStop();
         }
+        else if (action.equals(ACTION_DISMISS_PREVIOUS)) {
+            this.dismissPrevious(callbackContext);
+        }
+        else if (action.equals(ACTION_DISMISS_ALL)) {
+            this.dismissAll(callbackContext);
+        }
         else {
             return false;
         }
@@ -397,6 +408,33 @@
     }
 
     /**
+     * Close previously opened dialog
+     */
+    public synchronized void dismissPrevious(final CallbackContext callbackContext){
+        if(!dialogs.isEmpty()){
+            dialogs.remove(dialogs.size()-1).dismiss();
+            callbackContext.success();
+        }else{
+            callbackContext.error("No previously opened dialog to dismiss");
+        }
+    }
+
+    /**
+     * Close any open dialog.
+     */
+    public synchronized void dismissAll(final CallbackContext callbackContext){
+        if(!dialogs.isEmpty()){
+            for(AlertDialog dialog: dialogs){
+                dialog.dismiss();
+            }
+            dialogs = new ArrayList<AlertDialog>();
+            callbackContext.success();
+        }else{
+            callbackContext.error("No previously opened dialogs to dismiss");
+        }
+    }
+
+    /**
      * Show the spinner.
      *
      * @param title     Title of the dialog
@@ -517,7 +555,8 @@
     private void changeTextDirection(Builder dlg){
         int currentapiVersion = android.os.Build.VERSION.SDK_INT;
         dlg.create();
-        AlertDialog dialog =  dlg.show();
+        AlertDialog dialog = dlg.show();
+        dialogs.add(dialog);
         if (currentapiVersion >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
             TextView messageview = (TextView)dialog.findViewById(android.R.id.message);
             messageview.setTextDirection(android.view.View.TEXT_DIRECTION_LOCALE);
diff --git a/src/ios/CDVNotification.h b/src/ios/CDVNotification.h
index df06c72..fb0885c 100644
--- a/src/ios/CDVNotification.h
+++ b/src/ios/CDVNotification.h
@@ -28,5 +28,7 @@
 - (void)confirm:(CDVInvokedUrlCommand*)command;
 - (void)prompt:(CDVInvokedUrlCommand*)command;
 - (void)beep:(CDVInvokedUrlCommand*)command;
+- (void)dismissPrevious:(CDVInvokedUrlCommand*)command;
+- (void)dismissAll:(CDVInvokedUrlCommand*)command;
 
 @end
diff --git a/src/ios/CDVNotification.m b/src/ios/CDVNotification.m
index 92001a0..bea6466 100644
--- a/src/ios/CDVNotification.m
+++ b/src/ios/CDVNotification.m
@@ -24,6 +24,7 @@
 
 static void soundCompletionCallback(SystemSoundID ssid, void* data);
 static NSMutableArray *alertList = nil;
+static NSMutableArray *openAlertList = nil;
 
 @implementation CDVNotification
 
@@ -67,10 +68,11 @@
             }
 
             [weakNotif.commandDelegate sendPluginResult:result callbackId:callbackId];
+            [openAlertList removeObject:alertController];
         }]];
     }
     if ([dialogType isEqualToString:DIALOG_TYPE_PROMPT]) {
-        
+
         [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
             textField.text = defaultText;
         }];
@@ -159,15 +161,60 @@
 }
 
 -(void)presentAlertcontroller {
-    
+
     __weak CDVNotification* weakNotif = self;
-    [self.getTopPresentedViewController presentViewController:[alertList firstObject] animated:YES completion:^{
-        [alertList removeObject:[alertList firstObject]];
+    UIAlertController* alertController = [alertList firstObject];
+    [self.getTopPresentedViewController presentViewController:alertController animated:YES completion:^{
+        [alertList removeObject:alertController];
+        if (!openAlertList) {
+            openAlertList = [[NSMutableArray alloc] init];
+        }
+        [openAlertList addObject:alertController];
         if ([alertList count]>0) {
             [weakNotif presentAlertcontroller];
         }
     }];
-    
+
 }
 
+
+- (void)dismissPrevious:(CDVInvokedUrlCommand*)command
+{
+    if(openAlertList != nil && [openAlertList count]>0){
+        __weak CDVNotification* weakNotif = self;
+        UIAlertController* alertController = [openAlertList lastObject];
+        [alertController dismissViewControllerAnimated:NO completion:^{
+            [openAlertList removeObject:alertController];
+            [weakNotif.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] callbackId:command.callbackId];
+        }];
+    }else{
+        [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No previously opened dialog to dismiss"] callbackId:command.callbackId];
+    }
+}
+
+- (void)dismissAll:(CDVInvokedUrlCommand*)command
+{
+    if(openAlertList != nil && [openAlertList count]>0){
+        __weak CDVNotification* weakNotif = self;
+        [self dismissUIAlertControllers:^{
+            openAlertList = [[NSMutableArray alloc] init];
+            [weakNotif.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] callbackId:command.callbackId];
+        }];
+    }else{
+        [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No previously opened dialogs to dismiss"] callbackId:command.callbackId];
+    }
+}
+
+- (void)dismissUIAlertControllers:(void (^)(void))completeBlock{
+    if([self.getTopPresentedViewController isKindOfClass:[UIAlertController class]]){
+        __weak CDVNotification* weakNotif = self;
+        [self.getTopPresentedViewController dismissViewControllerAnimated:NO completion:^{
+            [weakNotif dismissUIAlertControllers:completeBlock];
+        }];
+    }else{
+        completeBlock();
+    }
+}
+
+
 @end
diff --git a/src/windows/NotificationProxy.js b/src/windows/NotificationProxy.js
index 1ccc18e..a3b04bb 100644
--- a/src/windows/NotificationProxy.js
+++ b/src/windows/NotificationProxy.js
@@ -261,6 +261,14 @@
         };
         snd.addEventListener('ended', onEvent);
         onEvent();
+    },
+
+    dismissPrevious: function () {
+        console.warn('dismissPrevious() is not available on browser platform');
+    },
+
+    dismissAll: function () {
+        console.warn('dismissAll() is not available on browser platform');
     }
 };
 
diff --git a/tests/tests.js b/tests/tests.js
index a6a67a4..7eb5ab7 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -46,6 +46,16 @@
             expect(typeof navigator.notification.prompt).toBeDefined();
             expect(typeof navigator.notification.prompt).toBe('function');
         });
+
+        it('should contain a dismissPrevious function', function () {
+            expect(typeof navigator.notification.dismissPrevious).toBeDefined();
+            expect(typeof navigator.notification.dismissPrevious).toBe('function');
+        });
+
+        it('should contain a dismissAll function', function () {
+            expect(typeof navigator.notification.dismissAll).toBeDefined();
+            expect(typeof navigator.notification.dismissAll).toBe('function');
+        });
     });
 };
 
@@ -143,7 +153,19 @@
         );
     };
 
+    var dismissPrevious = function (successCallback, errorCallback) {
+        console.log('dismissPrevious()');
+        navigator.notification.dismissPrevious(successCallback, errorCallback);
+    };
+
+    var dismissAll = function (successCallback, errorCallback) {
+        console.log('dismissAll()');
+        navigator.notification.dismissAll(successCallback, errorCallback);
+    };
+
     /******************************************************************************/
+    var isRunningOnAndroid = cordova.platformId === 'android';
+    var isRunningOniOS = cordova.platformId === 'ios';
 
     var dialogs_tests =
         '<div id="beep"></div>' +
@@ -167,6 +189,14 @@
         '<p/> <h3>CB-8947 Tests</h3><div id="cb8947"></div>' +
         'Expected results: Dialogs will not crash iOS';
 
+    if (isRunningOnAndroid || isRunningOniOS) {
+        dialogs_tests += '<h3>Dismissable dialogs</h3>' +
+            '<p/> <div id="dismiss_previous"></div>' +
+            'Expected results: 2 dialogs will open; one will automatically dismiss after 5 seconds' +
+            '<p/> <div id="dismiss_all"></div>' +
+            'Expected results: 2 dialogs will open; both will automatically dismiss after 5 seconds';
+    }
+
     contentEl.innerHTML = '<div id="info"></div>' + dialogs_tests;
 
     createActionButton(
@@ -306,4 +336,46 @@
         },
         'cb8947'
     );
+
+    // Dismissable dialogs (supported on Android & iOS only)
+    if (isRunningOnAndroid || isRunningOniOS) {
+        var open2Dialogs = function () {
+            var openDialogs = function () {
+                alertDialog('Alert Dialog 1 pressed', 'Alert Dialog 1', 'Continue');
+                alertDialog('Alert Dialog 2 pressed', 'Alert Dialog 2', 'Continue');
+            };
+            // dismiss any currently open dialogs first
+            dismissAll(openDialogs, openDialogs);
+        };
+
+        createActionButton(
+            'Dismiss Previous',
+            function () {
+                open2Dialogs();
+                setTimeout(function () {
+                    dismissPrevious(function () {
+                        console.log('Successfully dismissed previous dialog');
+                    }, function (error) {
+                        console.error('Failed to dismiss previous dialog: ' + error);
+                    });
+                }, 5000);
+            },
+            'dismiss_previous'
+        );
+
+        createActionButton(
+            'Dismiss All',
+            function () {
+                open2Dialogs();
+                setTimeout(function () {
+                    dismissAll(function () {
+                        console.log('Successfully dismissed all open dialogs');
+                    }, function (error) {
+                        console.error('Failed to dismiss all open dialogs: ' + error);
+                    });
+                }, 5000);
+            },
+            'dismiss_all'
+        );
+    }
 };
diff --git a/www/browser/notification.js b/www/browser/notification.js
index efcc376..ce1faa2 100644
--- a/www/browser/notification.js
+++ b/www/browser/notification.js
@@ -110,3 +110,11 @@
         }
     }
 };
+
+module.exports.dismissPrevious = window.navigator.notification.dismissPrevious = function () {
+    console.warn('dismissPrevious() is not available on browser platform');
+};
+
+module.exports.dismissAll = window.navigator.notification.dismissAll = function () {
+    console.warn('dismissAll() is not available on browser platform');
+};
diff --git a/www/notification.js b/www/notification.js
index 1de7b52..a06e2c9 100644
--- a/www/notification.js
+++ b/www/notification.js
@@ -107,6 +107,26 @@
     beep: function (count) {
         var defaultedCount = count || 1;
         exec(null, null, 'Notification', 'beep', [defaultedCount]);
+    },
+
+    /**
+     * Close previously opened dialog
+     *
+     * @param {Function} successCallback   The callback that is called when previously opened dialog has been dismissed.
+     * @param {Function} errorCallback   The callback that is called on failure to dismiss previously opened dialog.
+     */
+    dismissPrevious: function (successCallback, errorCallback) {
+        exec(successCallback, errorCallback, 'Notification', 'dismissPrevious', []);
+    },
+
+    /**
+     * Close any open dialog.
+     *
+     * @param {Function} successCallback   The callback that is called when all previously opened dialogs have been dismissed.
+     * @param {Function} errorCallback   The callback that is called on failure to dismiss all previously opened dialogs.
+     */
+    dismissAll: function (successCallback, errorCallback) {
+        exec(successCallback, errorCallback, 'Notification', 'dismissAll', []);
     }
 };