mirror of
https://github.com/apache/cordova-android.git
synced 2025-03-16 08:21:04 +08:00
CB-8917: New Plugin API for passing results on resume after Activity destruction
This commit is contained in:
parent
c30eeee5d8
commit
f5271431fb
16
cordova-js-src/platform.js
vendored
16
cordova-js-src/platform.js
vendored
@ -79,12 +79,26 @@ function onMessageFromNative(msg) {
|
|||||||
case 'searchbutton':
|
case 'searchbutton':
|
||||||
// App life cycle events
|
// App life cycle events
|
||||||
case 'pause':
|
case 'pause':
|
||||||
case 'resume':
|
|
||||||
// Volume events
|
// Volume events
|
||||||
case 'volumedownbutton':
|
case 'volumedownbutton':
|
||||||
case 'volumeupbutton':
|
case 'volumeupbutton':
|
||||||
cordova.fireDocumentEvent(action);
|
cordova.fireDocumentEvent(action);
|
||||||
break;
|
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:
|
default:
|
||||||
throw new Error('Unknown event action ' + action);
|
throw new Error('Unknown event action ' + action);
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ public class CallbackContext {
|
|||||||
|
|
||||||
private String callbackId;
|
private String callbackId;
|
||||||
private CordovaWebView webView;
|
private CordovaWebView webView;
|
||||||
private boolean finished;
|
protected boolean finished;
|
||||||
private int changingThreads;
|
private int changingThreads;
|
||||||
|
|
||||||
public CallbackContext(String callbackId, CordovaWebView webView) {
|
public CallbackContext(String callbackId, CordovaWebView webView) {
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
package org.apache.cordova;
|
package org.apache.cordova;
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
@ -28,6 +27,7 @@ import android.os.Bundle;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
@ -46,6 +46,8 @@ public class CordovaInterfaceImpl implements CordovaInterface {
|
|||||||
protected CordovaPlugin permissionResultCallback;
|
protected CordovaPlugin permissionResultCallback;
|
||||||
protected String initCallbackService;
|
protected String initCallbackService;
|
||||||
protected int activityResultRequestCode;
|
protected int activityResultRequestCode;
|
||||||
|
protected boolean activityWasDestroyed = false;
|
||||||
|
protected Bundle savedPluginState;
|
||||||
|
|
||||||
public CordovaInterfaceImpl(Activity activity) {
|
public CordovaInterfaceImpl(Activity activity) {
|
||||||
this(activity, Executors.newCachedThreadPool());
|
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) {
|
public void onCordovaInit(PluginManager pluginManager) {
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
if (savedResult != null) {
|
if (savedResult != null) {
|
||||||
onActivityResult(savedResult.requestCode, savedResult.resultCode, savedResult.intent);
|
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);
|
savedResult = new ActivityResultHolder(requestCode, resultCode, intent);
|
||||||
if (pluginManager != null) {
|
if (pluginManager != null) {
|
||||||
callback = pluginManager.getPlugin(initCallbackService);
|
callback = pluginManager.getPlugin(initCallbackService);
|
||||||
|
if(callback != null) {
|
||||||
|
callback.onRestoreStateForActivityResult(savedPluginState.getBundle(callback.getServiceName()),
|
||||||
|
new ResumeCallback(callback.getServiceName(), pluginManager));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
activityResultCallback = null;
|
activityResultCallback = null;
|
||||||
@ -126,7 +148,7 @@ public class CordovaInterfaceImpl implements CordovaInterface {
|
|||||||
callback.onActivityResult(requestCode, resultCode, intent);
|
callback.onActivityResult(requestCode, resultCode, intent);
|
||||||
return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,6 +169,8 @@ public class CordovaInterfaceImpl implements CordovaInterface {
|
|||||||
String serviceName = activityResultCallback.getServiceName();
|
String serviceName = activityResultCallback.getServiceName();
|
||||||
outState.putString("callbackService", serviceName);
|
outState.putString("callbackService", serviceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outState.putBundle("plugin", pluginManager.onSaveInstanceState());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,6 +178,8 @@ public class CordovaInterfaceImpl implements CordovaInterface {
|
|||||||
*/
|
*/
|
||||||
public void restoreInstanceState(Bundle savedInstanceState) {
|
public void restoreInstanceState(Bundle savedInstanceState) {
|
||||||
initCallbackService = savedInstanceState.getString("callbackService");
|
initCallbackService = savedInstanceState.getString("callbackService");
|
||||||
|
savedPluginState = savedInstanceState.getBundle("plugin");
|
||||||
|
activityWasDestroyed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ActivityResultHolder {
|
private static class ActivityResultHolder {
|
||||||
@ -209,6 +235,4 @@ public class CordovaInterfaceImpl implements CordovaInterface {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import android.content.pm.PackageManager;
|
|||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -174,6 +175,29 @@ public class CordovaPlugin {
|
|||||||
public void onDestroy() {
|
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.
|
* Called when a message is sent to plugin.
|
||||||
*
|
*
|
||||||
|
@ -445,7 +445,10 @@ public class CordovaWebViewImpl implements CordovaWebView {
|
|||||||
// Resume JavaScript timers. This affects all webviews within the app!
|
// Resume JavaScript timers. This affects all webviews within the app!
|
||||||
engine.setPaused(false);
|
engine.setPaused(false);
|
||||||
this.pluginManager.onResume(keepRunning);
|
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) {
|
if (hasPausedEver) {
|
||||||
sendJavascriptEvent("resume");
|
sendJavascriptEvent("resume");
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,6 @@
|
|||||||
|
|
||||||
package org.apache.cordova;
|
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.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@ -45,6 +41,8 @@ class CoreAndroid extends CordovaPlugin {
|
|||||||
protected static final String TAG = "CordovaApp";
|
protected static final String TAG = "CordovaApp";
|
||||||
private BroadcastReceiver telephonyReceiver;
|
private BroadcastReceiver telephonyReceiver;
|
||||||
private CallbackContext messageChannel;
|
private CallbackContext messageChannel;
|
||||||
|
private PluginResult pendingResume;
|
||||||
|
private final Object messageChannelLock = new Object();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send an event to be fired on the Javascript side.
|
* Send an event to be fired on the Javascript side.
|
||||||
@ -112,7 +110,13 @@ class CoreAndroid extends CordovaPlugin {
|
|||||||
this.exitApp();
|
this.exitApp();
|
||||||
}
|
}
|
||||||
else if (action.equals("messageChannel")) {
|
else if (action.equals("messageChannel")) {
|
||||||
|
synchronized(messageChannelLock) {
|
||||||
messageChannel = callbackContext;
|
messageChannel = callbackContext;
|
||||||
|
if (pendingResume != null) {
|
||||||
|
sendEventMessage(pendingResume);
|
||||||
|
pendingResume = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,10 +317,13 @@ class CoreAndroid extends CordovaPlugin {
|
|||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
LOG.e(TAG, "Failed to create event message", e);
|
LOG.e(TAG, "Failed to create event message", e);
|
||||||
}
|
}
|
||||||
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, obj);
|
sendEventMessage(new PluginResult(PluginResult.Status.OK, obj));
|
||||||
pluginResult.setKeepCallback(true);
|
}
|
||||||
|
|
||||||
|
private void sendEventMessage(PluginResult payload) {
|
||||||
|
payload.setKeepCallback(true);
|
||||||
if (messageChannel != null) {
|
if (messageChannel != null) {
|
||||||
messageChannel.sendPluginResult(pluginResult);
|
messageChannel.sendPluginResult(payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,4 +335,23 @@ class CoreAndroid extends CordovaPlugin {
|
|||||||
{
|
{
|
||||||
webView.getContext().unregisterReceiver(this.telephonyReceiver);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import org.json.JSONException;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.Debug;
|
import android.os.Debug;
|
||||||
import android.util.Log;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
76
framework/src/org/apache/cordova/ResumeCallback.java
Normal file
76
framework/src/org/apache/cordova/ResumeCallback.java
Normal file
@ -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));
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user