Skip to content

Commit

Permalink
CB-8917: New Plugin API for passing results on resume after Activity …
Browse files Browse the repository at this point in the history
…destruction
  • Loading branch information
riknoll committed Dec 2, 2015
1 parent c30eeee commit f527143
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 21 deletions.
16 changes: 15 additions & 1 deletion cordova-js-src/platform.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion framework/src/org/apache/cordova/CallbackContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
34 changes: 29 additions & 5 deletions framework/src/org/apache/cordova/CordovaInterfaceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ Licensed to the Apache Software Foundation (ASF) under one

package org.apache.cordova;

import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
Expand All @@ -28,6 +27,7 @@ Licensed to the Apache Software Foundation (ASF) under one
import android.util.Log;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
Expand All @@ -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());
Expand Down Expand Up @@ -95,12 +97,28 @@ public ExecutorService getThreadPool() {
}

/**
* 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));
}
}
}

Expand All @@ -115,6 +133,10 @@ public boolean onActivityResult(int requestCode, int resultCode, Intent intent)
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;
Expand All @@ -126,7 +148,7 @@ public boolean onActivityResult(int requestCode, int resultCode, Intent intent)
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;
}

Expand All @@ -147,13 +169,17 @@ public void onSaveInstanceState(Bundle outState) {
String serviceName = activityResultCallback.getServiceName();
outState.putString("callbackService", serviceName);
}

outState.putBundle("plugin", pluginManager.onSaveInstanceState());
}

/**
* Call this from onCreate() so that any saved startActivityForResult parameters will be restored.
*/
public void restoreInstanceState(Bundle savedInstanceState) {
initCallbackService = savedInstanceState.getString("callbackService");
savedPluginState = savedInstanceState.getBundle("plugin");
activityWasDestroyed = true;
}

private static class ActivityResultHolder {
Expand Down Expand Up @@ -209,6 +235,4 @@ public boolean hasPermission(String permission)
return true;
}
}


}
34 changes: 29 additions & 5 deletions framework/src/org/apache/cordova/CordovaPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Licensed to the Apache Software Foundation (ASF) under one
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;
Expand Down Expand Up @@ -77,7 +78,7 @@ protected void pluginInitialize() {
public String getServiceName() {
return serviceName;
}

/**
* Executes the request.
*
Expand Down Expand Up @@ -174,6 +175,29 @@ public void onNewIntent(Intent intent) {
public void onDestroy() {
}

/**
* 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.
*
Expand Down Expand Up @@ -323,7 +347,7 @@ protected Uri fromPluginUri(Uri pluginUri) {
*/
public void onReset() {
}

/**
* Called when the system received an HTTP authentication request. Plugin can use
* the supplied HttpAuthHandler to process this auth challenge.
Expand All @@ -332,14 +356,14 @@ public void onReset() {
* @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.
Expand Down
5 changes: 4 additions & 1 deletion framework/src/org/apache/cordova/CordovaWebViewImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,10 @@ public void handleResume(boolean keepRunning) {
// 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");
}
Expand Down
42 changes: 34 additions & 8 deletions framework/src/org/apache/cordova/CoreAndroid.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ Licensed to the Apache Software Foundation (ASF) under one

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;
Expand All @@ -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.
Expand Down Expand Up @@ -112,7 +110,13 @@ else if (action.equals("exitApp")) {
this.exitApp();
}
else if (action.equals("messageChannel")) {
messageChannel = callbackContext;
synchronized(messageChannelLock) {
messageChannel = callbackContext;
if (pendingResume != null) {
sendEventMessage(pendingResume);
pendingResume = null;
}
}
return true;
}

Expand Down Expand Up @@ -313,10 +317,13 @@ private void sendEventMessage(String action) {
} 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);
}
}

Expand All @@ -328,4 +335,23 @@ public void onDestroy()
{
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;
}
}
}
}
13 changes: 13 additions & 0 deletions framework/src/org/apache/cordova/PluginManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Licensed to the Apache Software Foundation (ASF) under one
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;

Expand Down Expand Up @@ -511,4 +512,16 @@ public void onConfigurationChanged(Configuration newConfig) {
}
}

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;
}
}
Loading

0 comments on commit f527143

Please sign in to comment.