Repository: cordova-android
Updated Branches:
  refs/heads/master c30eeee5d -> f5271431f


CB-8917: New Plugin API for passing results on resume after Activity destruction


Project: http://git-wip-us.apache.org/repos/asf/cordova-android/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-android/commit/f5271431
Tree: http://git-wip-us.apache.org/repos/asf/cordova-android/tree/f5271431
Diff: http://git-wip-us.apache.org/repos/asf/cordova-android/diff/f5271431

Branch: refs/heads/master
Commit: f5271431fbcda70188f485d9e91541bb596551fe
Parents: c30eeee
Author: riknoll <[email protected]>
Authored: Tue Nov 10 15:01:13 2015 -0800
Committer: riknoll <[email protected]>
Committed: Wed Dec 2 09:49:49 2015 -0800

----------------------------------------------------------------------
 cordova-js-src/platform.js                      | 16 ++++-
 .../src/org/apache/cordova/CallbackContext.java |  2 +-
 .../apache/cordova/CordovaInterfaceImpl.java    | 34 +++++++--
 .../src/org/apache/cordova/CordovaPlugin.java   | 34 +++++++--
 .../org/apache/cordova/CordovaWebViewImpl.java  |  5 +-
 .../src/org/apache/cordova/CoreAndroid.java     | 42 ++++++++---
 .../src/org/apache/cordova/PluginManager.java   | 13 ++++
 .../src/org/apache/cordova/ResumeCallback.java  | 76 ++++++++++++++++++++
 8 files changed, 201 insertions(+), 21 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-android/blob/f5271431/cordova-js-src/platform.js
----------------------------------------------------------------------
diff --git a/cordova-js-src/platform.js b/cordova-js-src/platform.js
index bffc675..0706a34 100644
--- a/cordova-js-src/platform.js
+++ b/cordova-js-src/platform.js
@@ -79,12 +79,26 @@ function onMessageFromNative(msg) {
         case 'searchbutton':
         // App life cycle events
         case 'pause':
-        case 'resume':
         // Volume events
         case 'volumedownbutton':
         case 'volumeupbutton':
             cordova.fireDocumentEvent(action);
             break;
+        case 'resume':
+            if(arguments.length > 1 && msg.pendingResult) {
+                if(arguments.length === 2) {
+                    msg.pendingResult.result = arguments[1];
+                } else {
+                    // The plugin returned a multipart message
+                    var res = [];
+                    for(var i = 1; i < arguments.length; i++) {
+                        res.push(arguments[i]);
+                    }
+                    msg.pendingResult.result = res;
+                }
+            }
+            cordova.fireDocumentEvent(action, msg);
+            break;
         default:
             throw new Error('Unknown event action ' + action);
     }

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/f5271431/framework/src/org/apache/cordova/CallbackContext.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CallbackContext.java 
b/framework/src/org/apache/cordova/CallbackContext.java
index 446c37d..4c0d7b9 100644
--- a/framework/src/org/apache/cordova/CallbackContext.java
+++ b/framework/src/org/apache/cordova/CallbackContext.java
@@ -31,7 +31,7 @@ public class CallbackContext {
 
     private String callbackId;
     private CordovaWebView webView;
-    private boolean finished;
+    protected boolean finished;
     private int changingThreads;
 
     public CallbackContext(String callbackId, CordovaWebView webView) {

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/f5271431/framework/src/org/apache/cordova/CordovaInterfaceImpl.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CordovaInterfaceImpl.java 
b/framework/src/org/apache/cordova/CordovaInterfaceImpl.java
index 3f5e69d..65e2a90 100644
--- a/framework/src/org/apache/cordova/CordovaInterfaceImpl.java
+++ b/framework/src/org/apache/cordova/CordovaInterfaceImpl.java
@@ -19,7 +19,6 @@
 
 package org.apache.cordova;
 
-import android.Manifest;
 import android.app.Activity;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -28,6 +27,7 @@ import android.os.Bundle;
 import android.util.Log;
 
 import org.json.JSONException;
+import org.json.JSONObject;
 
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -46,6 +46,8 @@ public class CordovaInterfaceImpl implements CordovaInterface 
{
     protected CordovaPlugin permissionResultCallback;
     protected String initCallbackService;
     protected int activityResultRequestCode;
+    protected boolean activityWasDestroyed = false;
+    protected Bundle savedPluginState;
 
     public CordovaInterfaceImpl(Activity activity) {
         this(activity, Executors.newCachedThreadPool());
@@ -95,12 +97,28 @@ public class CordovaInterfaceImpl implements 
CordovaInterface {
     }
 
     /**
-     * Dispatches any pending onActivityResult callbacks.
+     * Dispatches any pending onActivityResult callbacks and sends the resume 
event if the
+     * Activity was destroyed by the OS.
      */
     public void onCordovaInit(PluginManager pluginManager) {
         this.pluginManager = pluginManager;
         if (savedResult != null) {
             onActivityResult(savedResult.requestCode, savedResult.resultCode, 
savedResult.intent);
+        } else if(activityWasDestroyed) {
+            // If there was no Activity result, we still need to send out the 
resume event if the
+            // Activity was destroyed by the OS
+            activityWasDestroyed = false;
+
+            CoreAndroid appPlugin = (CoreAndroid) 
pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
+            if(appPlugin != null) {
+                JSONObject obj = new JSONObject();
+                try {
+                    obj.put("action", "resume");
+                } catch (JSONException e) {
+                    LOG.e(TAG, "Failed to create event message", e);
+                }
+                appPlugin.sendResumeEvent(new 
PluginResult(PluginResult.Status.OK, obj));
+            }
         }
     }
 
@@ -115,6 +133,10 @@ public class CordovaInterfaceImpl implements 
CordovaInterface {
             savedResult = new ActivityResultHolder(requestCode, resultCode, 
intent);
             if (pluginManager != null) {
                 callback = pluginManager.getPlugin(initCallbackService);
+                if(callback != null) {
+                    
callback.onRestoreStateForActivityResult(savedPluginState.getBundle(callback.getServiceName()),
+                            new ResumeCallback(callback.getServiceName(), 
pluginManager));
+                }
             }
         }
         activityResultCallback = null;
@@ -126,7 +148,7 @@ public class CordovaInterfaceImpl implements 
CordovaInterface {
             callback.onActivityResult(requestCode, resultCode, intent);
             return true;
         }
-        Log.w(TAG, "Got an activity result, but no plugin was registered to 
receive it" + (savedResult != null ? " yet!": "."));
+        Log.w(TAG, "Got an activity result, but no plugin was registered to 
receive it" + (savedResult != null ? " yet!" : "."));
         return false;
     }
 
@@ -147,6 +169,8 @@ public class CordovaInterfaceImpl implements 
CordovaInterface {
             String serviceName = activityResultCallback.getServiceName();
             outState.putString("callbackService", serviceName);
         }
+
+        outState.putBundle("plugin", pluginManager.onSaveInstanceState());
     }
 
     /**
@@ -154,6 +178,8 @@ public class CordovaInterfaceImpl implements 
CordovaInterface {
      */
     public void restoreInstanceState(Bundle savedInstanceState) {
         initCallbackService = savedInstanceState.getString("callbackService");
+        savedPluginState = savedInstanceState.getBundle("plugin");
+        activityWasDestroyed = true;
     }
 
     private static class ActivityResultHolder {
@@ -209,6 +235,4 @@ public class CordovaInterfaceImpl implements 
CordovaInterface {
             return true;
         }
     }
-
-
 }

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/f5271431/framework/src/org/apache/cordova/CordovaPlugin.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CordovaPlugin.java 
b/framework/src/org/apache/cordova/CordovaPlugin.java
index 4627ebb..41af1db 100644
--- a/framework/src/org/apache/cordova/CordovaPlugin.java
+++ b/framework/src/org/apache/cordova/CordovaPlugin.java
@@ -30,6 +30,7 @@ import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.net.Uri;
 import android.os.Build;
+import android.os.Bundle;
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -77,7 +78,7 @@ public class CordovaPlugin {
     public String getServiceName() {
         return serviceName;
     }
-    
+
     /**
      * Executes the request.
      *
@@ -175,6 +176,29 @@ public class CordovaPlugin {
     }
 
     /**
+     * Called when the Activity is being destroyed (e.g. if a plugin calls out 
to an external
+     * Activity and the OS kills the CordovaActivity in the background). The 
plugin should save its
+     * state in this method only if it is awaiting the result of an external 
Activity and needs
+     * to preserve some information so as to handle that result; 
onRestoreStateForActivityResult()
+     * will only be called if the plugin is the recipient of an Activity result
+     *
+     * @return  Bundle containing the state of the plugin or null if state 
does not need to be saved
+     */
+    public Bundle onSaveInstanceState() {
+        return null;
+    }
+
+    /**
+     * Called when a plugin is the recipient of an Activity result after the 
CordovaActivity has
+     * been destroyed. The Bundle will be the same as the one the plugin 
returned in
+     * onSaveInstanceState()
+     *
+     * @param state             Bundle containing the state of the plugin
+     * @param callbackContext   Replacement Context to return the plugin 
result to
+     */
+    public void onRestoreStateForActivityResult(Bundle state, CallbackContext 
callbackContext) {}
+
+    /**
      * Called when a message is sent to plugin.
      *
      * @param id            The message id
@@ -323,7 +347,7 @@ public class CordovaPlugin {
      */
     public void onReset() {
     }
-    
+
     /**
      * Called when the system received an HTTP authentication request. Plugin 
can use
      * the supplied HttpAuthHandler to process this auth challenge.
@@ -332,14 +356,14 @@ public class CordovaPlugin {
      * @param handler           The HttpAuthHandler used to set the WebView's 
response
      * @param host              The host requiring authentication
      * @param realm             The realm for which authentication is required
-     * 
+     *
      * @return                  Returns True if plugin will resolve this auth 
challenge, otherwise False
-     * 
+     *
      */
     public boolean onReceivedHttpAuthRequest(CordovaWebView view, 
ICordovaHttpAuthHandler handler, String host, String realm) {
         return false;
     }
-    
+
     /**
      * Called when he system received an SSL client certificate request.  
Plugin can use
      * the supplied ClientCertRequest to process this certificate challenge.

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/f5271431/framework/src/org/apache/cordova/CordovaWebViewImpl.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CordovaWebViewImpl.java 
b/framework/src/org/apache/cordova/CordovaWebViewImpl.java
index 06da55e..59a0de7 100644
--- a/framework/src/org/apache/cordova/CordovaWebViewImpl.java
+++ b/framework/src/org/apache/cordova/CordovaWebViewImpl.java
@@ -445,7 +445,10 @@ public class CordovaWebViewImpl implements CordovaWebView {
         // Resume JavaScript timers. This affects all webviews within the app!
         engine.setPaused(false);
         this.pluginManager.onResume(keepRunning);
-        // To be the same as other platforms, fire this event only when 
resumed after a "pause".
+
+        // In order to match the behavior of the other platforms, we only send 
onResume after an
+        // onPause has occurred. The resume event might still be sent if the 
Activity was killed
+        // while waiting for the result of an external Activity once the 
result is obtained
         if (hasPausedEver) {
             sendJavascriptEvent("resume");
         }

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/f5271431/framework/src/org/apache/cordova/CoreAndroid.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CoreAndroid.java 
b/framework/src/org/apache/cordova/CoreAndroid.java
index 000717a..90d079e 100755
--- a/framework/src/org/apache/cordova/CoreAndroid.java
+++ b/framework/src/org/apache/cordova/CoreAndroid.java
@@ -19,10 +19,6 @@
 
 package org.apache.cordova;
 
-import org.apache.cordova.CallbackContext;
-import org.apache.cordova.CordovaPlugin;
-import org.apache.cordova.LOG;
-import org.apache.cordova.PluginResult;
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -45,6 +41,8 @@ class CoreAndroid extends CordovaPlugin {
     protected static final String TAG = "CordovaApp";
     private BroadcastReceiver telephonyReceiver;
     private CallbackContext messageChannel;
+    private PluginResult pendingResume;
+    private final Object messageChannelLock = new Object();
 
     /**
      * Send an event to be fired on the Javascript side.
@@ -112,7 +110,13 @@ class CoreAndroid extends CordovaPlugin {
                 this.exitApp();
             }
                        else if (action.equals("messageChannel")) {
-                messageChannel = callbackContext;
+                synchronized(messageChannelLock) {
+                    messageChannel = callbackContext;
+                    if (pendingResume != null) {
+                        sendEventMessage(pendingResume);
+                        pendingResume = null;
+                    }
+                }
                 return true;
             }
 
@@ -313,10 +317,13 @@ class CoreAndroid extends CordovaPlugin {
         } catch (JSONException e) {
             LOG.e(TAG, "Failed to create event message", e);
         }
-        PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, 
obj);
-        pluginResult.setKeepCallback(true);
+        sendEventMessage(new PluginResult(PluginResult.Status.OK, obj));
+    }
+
+    private void sendEventMessage(PluginResult payload) {
+        payload.setKeepCallback(true);
         if (messageChannel != null) {
-            messageChannel.sendPluginResult(pluginResult);
+            messageChannel.sendPluginResult(payload);
         }
     }
 
@@ -328,4 +335,23 @@ class CoreAndroid extends CordovaPlugin {
     {
         webView.getContext().unregisterReceiver(this.telephonyReceiver);
     }
+
+    /**
+     * Used to send the resume event in the case that the Activity is 
destroyed by the OS
+     *
+     * @param resumeEvent PluginResult containing the payload for the resume 
event to be fired
+     */
+    public void sendResumeEvent(PluginResult resumeEvent) {
+        // This operation must be synchronized because plugin results that 
trigger resume
+        // events can be processed asynchronously
+        synchronized(messageChannelLock) {
+            if (messageChannel != null) {
+                sendEventMessage(resumeEvent);
+            } else {
+                // Might get called before the page loads, so we need to store 
it until the
+                // messageChannel gets created
+                this.pendingResume = resumeEvent;
+            }
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/f5271431/framework/src/org/apache/cordova/PluginManager.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/PluginManager.java 
b/framework/src/org/apache/cordova/PluginManager.java
index 3afbc18..64147da 100755
--- a/framework/src/org/apache/cordova/PluginManager.java
+++ b/framework/src/org/apache/cordova/PluginManager.java
@@ -26,6 +26,7 @@ import org.json.JSONException;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Debug;
 import android.util.Log;
 
@@ -511,4 +512,16 @@ public class PluginManager {
         }
     }
 
+    public Bundle onSaveInstanceState() {
+        Bundle state = new Bundle();
+        for (CordovaPlugin plugin : this.pluginMap.values()) {
+            if (plugin != null) {
+                Bundle pluginState = plugin.onSaveInstanceState();
+                if(pluginState != null) {
+                    state.putBundle(plugin.getServiceName(), pluginState);
+                }
+            }
+        }
+        return state;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/f5271431/framework/src/org/apache/cordova/ResumeCallback.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/ResumeCallback.java 
b/framework/src/org/apache/cordova/ResumeCallback.java
new file mode 100644
index 0000000..49a43b5
--- /dev/null
+++ b/framework/src/org/apache/cordova/ResumeCallback.java
@@ -0,0 +1,76 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+package org.apache.cordova;
+
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ResumeCallback extends CallbackContext {
+    private final String TAG = "CordovaResumeCallback";
+    private String serviceName;
+    private PluginManager pluginManager;
+
+    public ResumeCallback(String serviceName, PluginManager pluginManager) {
+        super("resumecallback", null);
+        this.serviceName = serviceName;
+        this.pluginManager = pluginManager;
+    }
+
+    @Override
+    public void sendPluginResult(PluginResult pluginResult) {
+        synchronized (this) {
+            if (finished) {
+                LOG.w(TAG, serviceName + " attempted to send a second callback 
to ResumeCallback\nResult was: " + pluginResult.getMessage());
+                return;
+            } else {
+                finished = true;
+            }
+        }
+
+        JSONObject event = new JSONObject();
+        JSONObject pluginResultObject = new JSONObject();
+
+        try {
+            pluginResultObject.put("pluginServiceName", this.serviceName);
+            pluginResultObject.put("pluginStatus", 
PluginResult.StatusMessages[pluginResult.getStatus()]);
+
+            event.put("action", "resume");
+            event.put("pendingResult", pluginResultObject);
+        } catch (JSONException e) {
+            LOG.e(TAG, "Unable to create resume object for Activity Result");
+        }
+
+        PluginResult eventResult = new PluginResult(PluginResult.Status.OK, 
event);
+
+        // We send a list of results to the js so that we don't have to decode
+        // the PluginResult passed to this CallbackContext into JSON twice.
+        // The results are combined into an event payload before the event is
+        // fired on the js side of things (see platform.js)
+        List<PluginResult> result = new ArrayList<PluginResult>();
+        result.add(eventResult);
+        result.add(pluginResult);
+
+        CoreAndroid appPlugin = (CoreAndroid) 
pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
+        appPlugin.sendResumeEvent(new PluginResult(PluginResult.Status.OK, 
result));
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to