http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaWebViewImpl.java ---------------------------------------------------------------------- diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaWebViewImpl.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaWebViewImpl.java new file mode 100644 index 0000000..85a0b5f --- /dev/null +++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaWebViewImpl.java @@ -0,0 +1,613 @@ +/* + 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 android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebChromeClient; +import android.widget.FrameLayout; + +import org.apache.cordova.engine.SystemWebViewEngine; +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Main class for interacting with a Cordova webview. Manages plugins, events, and a CordovaWebViewEngine. + * Class uses two-phase initialization. You must call init() before calling any other methods. + */ +public class CordovaWebViewImpl implements CordovaWebView { + + public static final String TAG = "CordovaWebViewImpl"; + + private PluginManager pluginManager; + + protected final CordovaWebViewEngine engine; + private CordovaInterface cordova; + + // Flag to track that a loadUrl timeout occurred + private int loadUrlTimeout = 0; + + private CordovaResourceApi resourceApi; + private CordovaPreferences preferences; + private CoreAndroid appPlugin; + private NativeToJsMessageQueue nativeToJsMessageQueue; + private EngineClient engineClient = new EngineClient(); + private boolean hasPausedEver; + + // The URL passed to loadUrl(), not necessarily the URL of the current page. + String loadedUrl; + + /** custom view created by the browser (a video player for example) */ + private View mCustomView; + private WebChromeClient.CustomViewCallback mCustomViewCallback; + + private Set<Integer> boundKeyCodes = new HashSet<Integer>(); + + public static CordovaWebViewEngine createEngine(Context context, CordovaPreferences preferences) { + String className = preferences.getString("webview", SystemWebViewEngine.class.getCanonicalName()); + try { + Class<?> webViewClass = Class.forName(className); + Constructor<?> constructor = webViewClass.getConstructor(Context.class, CordovaPreferences.class); + return (CordovaWebViewEngine) constructor.newInstance(context, preferences); + } catch (Exception e) { + throw new RuntimeException("Failed to create webview. ", e); + } + } + + public CordovaWebViewImpl(CordovaWebViewEngine cordovaWebViewEngine) { + this.engine = cordovaWebViewEngine; + } + + // Convenience method for when creating programmatically (not from Config.xml). + public void init(CordovaInterface cordova) { + init(cordova, new ArrayList<PluginEntry>(), new CordovaPreferences()); + } + + @Override + public void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences) { + if (this.cordova != null) { + throw new IllegalStateException(); + } + this.cordova = cordova; + this.preferences = preferences; + pluginManager = new PluginManager(this, this.cordova, pluginEntries); + resourceApi = new CordovaResourceApi(engine.getView().getContext(), pluginManager); + nativeToJsMessageQueue = new NativeToJsMessageQueue(); + nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.NoOpBridgeMode()); + nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.LoadUrlBridgeMode(engine, cordova)); + + if (preferences.getBoolean("DisallowOverscroll", false)) { + engine.getView().setOverScrollMode(View.OVER_SCROLL_NEVER); + } + engine.init(this, cordova, engineClient, resourceApi, pluginManager, nativeToJsMessageQueue); + // This isn't enforced by the compiler, so assert here. + assert engine.getView() instanceof CordovaWebViewEngine.EngineView; + + pluginManager.addService(CoreAndroid.PLUGIN_NAME, "org.apache.cordova.CoreAndroid"); + pluginManager.init(); + + } + + @Override + public boolean isInitialized() { + return cordova != null; + } + + @Override + public void loadUrlIntoView(final String url, boolean recreatePlugins) { + LOG.d(TAG, ">>> loadUrl(" + url + ")"); + if (url.equals("about:blank") || url.startsWith("javascript:")) { + engine.loadUrl(url, false); + return; + } + + recreatePlugins = recreatePlugins || (loadedUrl == null); + + if (recreatePlugins) { + // Don't re-initialize on first load. + if (loadedUrl != null) { + appPlugin = null; + pluginManager.init(); + } + loadedUrl = url; + } + + // Create a timeout timer for loadUrl + final int currentLoadUrlTimeout = loadUrlTimeout; + final int loadUrlTimeoutValue = preferences.getInteger("LoadUrlTimeoutValue", 20000); + + // Timeout error method + final Runnable loadError = new Runnable() { + public void run() { + stopLoading(); + LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!"); + + // Handle other errors by passing them to the webview in JS + JSONObject data = new JSONObject(); + try { + data.put("errorCode", -6); + data.put("description", "The connection to the server was unsuccessful."); + data.put("url", url); + } catch (JSONException e) { + // Will never happen. + } + pluginManager.postMessage("onReceivedError", data); + } + }; + + // Timeout timer method + final Runnable timeoutCheck = new Runnable() { + public void run() { + try { + synchronized (this) { + wait(loadUrlTimeoutValue); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // If timeout, then stop loading and handle error + if (loadUrlTimeout == currentLoadUrlTimeout) { + cordova.getActivity().runOnUiThread(loadError); + } + } + }; + + final boolean _recreatePlugins = recreatePlugins; + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + if (loadUrlTimeoutValue > 0) { + cordova.getThreadPool().execute(timeoutCheck); + } + engine.loadUrl(url, _recreatePlugins); + } + }); + } + + + @Override + public void loadUrl(String url) { + loadUrlIntoView(url, true); + } + + @Override + public void showWebPage(String url, boolean openExternal, boolean clearHistory, Map<String, Object> params) { + LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap)", url, openExternal, clearHistory); + + // If clearing history + if (clearHistory) { + engine.clearHistory(); + } + + // If loading into our webview + if (!openExternal) { + // Make sure url is in whitelist + if (pluginManager.shouldAllowNavigation(url)) { + // TODO: What about params? + // Load new URL + loadUrlIntoView(url, true); + } else { + LOG.w(TAG, "showWebPage: Refusing to load URL into webview since it is not in the <allow-navigation> whitelist. URL=" + url); + } + } + if (!pluginManager.shouldOpenExternalUrl(url)) { + LOG.w(TAG, "showWebPage: Refusing to send intent for URL since it is not in the <allow-intent> whitelist. URL=" + url); + return; + } + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + // To send an intent without CATEGORY_BROWSER, a custom plugin should be used. + intent.addCategory(Intent.CATEGORY_BROWSABLE); + Uri uri = Uri.parse(url); + // Omitting the MIME type for file: URLs causes "No Activity found to handle Intent". + // Adding the MIME type to http: URLs causes them to not be handled by the downloader. + if ("file".equals(uri.getScheme())) { + intent.setDataAndType(uri, resourceApi.getMimeType(uri)); + } else { + intent.setData(uri); + } + cordova.getActivity().startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + LOG.e(TAG, "Error loading url " + url, e); + } + } + + @Override + @Deprecated + public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) { + // This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0 + LOG.d(TAG, "showing Custom View"); + // if a view already exists then immediately terminate the new one + if (mCustomView != null) { + callback.onCustomViewHidden(); + return; + } + + // Store the view and its callback for later (to kill it properly) + mCustomView = view; + mCustomViewCallback = callback; + + // Add the custom view to its container. + ViewGroup parent = (ViewGroup) engine.getView().getParent(); + parent.addView(view, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + Gravity.CENTER)); + + // Hide the content view. + engine.getView().setVisibility(View.GONE); + + // Finally show the custom view container. + parent.setVisibility(View.VISIBLE); + parent.bringToFront(); + } + + @Override + @Deprecated + public void hideCustomView() { + // This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0 + if (mCustomView == null) return; + LOG.d(TAG, "Hiding Custom View"); + + // Hide the custom view. + mCustomView.setVisibility(View.GONE); + + // Remove the custom view from its container. + ViewGroup parent = (ViewGroup) engine.getView().getParent(); + parent.removeView(mCustomView); + mCustomView = null; + mCustomViewCallback.onCustomViewHidden(); + + // Show the content view. + engine.getView().setVisibility(View.VISIBLE); + } + + @Override + @Deprecated + public boolean isCustomViewShowing() { + return mCustomView != null; + } + + @Override + @Deprecated + public void sendJavascript(String statement) { + nativeToJsMessageQueue.addJavaScript(statement); + } + + @Override + public void sendPluginResult(PluginResult cr, String callbackId) { + nativeToJsMessageQueue.addPluginResult(cr, callbackId); + } + + @Override + public PluginManager getPluginManager() { + return pluginManager; + } + @Override + public CordovaPreferences getPreferences() { + return preferences; + } + @Override + public ICordovaCookieManager getCookieManager() { + return engine.getCookieManager(); + } + @Override + public CordovaResourceApi getResourceApi() { + return resourceApi; + } + @Override + public CordovaWebViewEngine getEngine() { + return engine; + } + @Override + public View getView() { + return engine.getView(); + } + @Override + public Context getContext() { + return engine.getView().getContext(); + } + + private void sendJavascriptEvent(String event) { + if (appPlugin == null) { + appPlugin = (CoreAndroid)pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME); + } + + if (appPlugin == null) { + LOG.w(TAG, "Unable to fire event without existing plugin"); + return; + } + appPlugin.fireJavascriptEvent(event); + } + + @Override + public void setButtonPlumbedToJs(int keyCode, boolean override) { + switch (keyCode) { + case KeyEvent.KEYCODE_VOLUME_DOWN: + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_BACK: + case KeyEvent.KEYCODE_MENU: + // TODO: Why are search and menu buttons handled separately? + if (override) { + boundKeyCodes.add(keyCode); + } else { + boundKeyCodes.remove(keyCode); + } + return; + default: + throw new IllegalArgumentException("Unsupported keycode: " + keyCode); + } + } + + @Override + public boolean isButtonPlumbedToJs(int keyCode) { + return boundKeyCodes.contains(keyCode); + } + + @Override + public Object postMessage(String id, Object data) { + return pluginManager.postMessage(id, data); + } + + // Engine method proxies: + @Override + public String getUrl() { + return engine.getUrl(); + } + + @Override + public void stopLoading() { + // Clear timeout flag + loadUrlTimeout++; + } + + @Override + public boolean canGoBack() { + return engine.canGoBack(); + } + + @Override + public void clearCache() { + engine.clearCache(); + } + + @Override + @Deprecated + public void clearCache(boolean b) { + engine.clearCache(); + } + + @Override + public void clearHistory() { + engine.clearHistory(); + } + + @Override + public boolean backHistory() { + return engine.goBack(); + } + + /////// LifeCycle methods /////// + @Override + public void onNewIntent(Intent intent) { + if (this.pluginManager != null) { + this.pluginManager.onNewIntent(intent); + } + } + @Override + public void handlePause(boolean keepRunning) { + if (!isInitialized()) { + return; + } + hasPausedEver = true; + pluginManager.onPause(keepRunning); + sendJavascriptEvent("pause"); + + // If app doesn't want to run in background + if (!keepRunning) { + // Pause JavaScript timers. This affects all webviews within the app! + engine.setPaused(true); + } + } + @Override + public void handleResume(boolean keepRunning) { + if (!isInitialized()) { + return; + } + + // Resume JavaScript timers. This affects all webviews within the app! + engine.setPaused(false); + this.pluginManager.onResume(keepRunning); + + // 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"); + } + } + @Override + public void handleStart() { + if (!isInitialized()) { + return; + } + pluginManager.onStart(); + } + @Override + public void handleStop() { + if (!isInitialized()) { + return; + } + pluginManager.onStop(); + } + @Override + public void handleDestroy() { + if (!isInitialized()) { + return; + } + // Cancel pending timeout timer. + loadUrlTimeout++; + + // Forward to plugins + this.pluginManager.onDestroy(); + + // TODO: about:blank is a bit special (and the default URL for new frames) + // We should use a blank data: url instead so it's more obvious + this.loadUrl("about:blank"); + + // TODO: Should not destroy webview until after about:blank is done loading. + engine.destroy(); + hideCustomView(); + } + + protected class EngineClient implements CordovaWebViewEngine.Client { + @Override + public void clearLoadTimeoutTimer() { + loadUrlTimeout++; + } + + @Override + public void onPageStarted(String newUrl) { + LOG.d(TAG, "onPageDidNavigate(" + newUrl + ")"); + boundKeyCodes.clear(); + pluginManager.onReset(); + pluginManager.postMessage("onPageStarted", newUrl); + } + + @Override + public void onReceivedError(int errorCode, String description, String failingUrl) { + clearLoadTimeoutTimer(); + JSONObject data = new JSONObject(); + try { + data.put("errorCode", errorCode); + data.put("description", description); + data.put("url", failingUrl); + } catch (JSONException e) { + e.printStackTrace(); + } + pluginManager.postMessage("onReceivedError", data); + } + + @Override + public void onPageFinishedLoading(String url) { + LOG.d(TAG, "onPageFinished(" + url + ")"); + + clearLoadTimeoutTimer(); + + // Broadcast message that page has loaded + pluginManager.postMessage("onPageFinished", url); + + // Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly + if (engine.getView().getVisibility() != View.VISIBLE) { + Thread t = new Thread(new Runnable() { + public void run() { + try { + Thread.sleep(2000); + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + pluginManager.postMessage("spinner", "stop"); + } + }); + } catch (InterruptedException e) { + } + } + }); + t.start(); + } + + // Shutdown if blank loaded + if (url.equals("about:blank")) { + pluginManager.postMessage("exit", null); + } + } + + @Override + public Boolean onDispatchKeyEvent(KeyEvent event) { + int keyCode = event.getKeyCode(); + boolean isBackButton = keyCode == KeyEvent.KEYCODE_BACK; + if (event.getAction() == KeyEvent.ACTION_DOWN) { + if (isBackButton && mCustomView != null) { + return true; + } else if (boundKeyCodes.contains(keyCode)) { + return true; + } else if (isBackButton) { + return engine.canGoBack(); + } + } else if (event.getAction() == KeyEvent.ACTION_UP) { + if (isBackButton && mCustomView != null) { + hideCustomView(); + return true; + } else if (boundKeyCodes.contains(keyCode)) { + String eventName = null; + switch (keyCode) { + case KeyEvent.KEYCODE_VOLUME_DOWN: + eventName = "volumedownbutton"; + break; + case KeyEvent.KEYCODE_VOLUME_UP: + eventName = "volumeupbutton"; + break; + case KeyEvent.KEYCODE_SEARCH: + eventName = "searchbutton"; + break; + case KeyEvent.KEYCODE_MENU: + eventName = "menubutton"; + break; + case KeyEvent.KEYCODE_BACK: + eventName = "backbutton"; + break; + } + if (eventName != null) { + sendJavascriptEvent(eventName); + return true; + } + } else if (isBackButton) { + return engine.goBack(); + } + } + return null; + } + + @Override + public boolean onNavigationAttempt(String url) { + // Give plugins the chance to handle the url + if (pluginManager.onOverrideUrlLoading(url)) { + return true; + } else if (pluginManager.shouldAllowNavigation(url)) { + return false; + } else if (pluginManager.shouldOpenExternalUrl(url)) { + showWebPage(url, true, false, null); + return true; + } + LOG.w(TAG, "Blocked (possibly sub-frame) navigation to non-allowed URL: " + url); + return true; + } + } +}
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CoreAndroid.java ---------------------------------------------------------------------- diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CoreAndroid.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CoreAndroid.java new file mode 100755 index 0000000..e384f8d --- /dev/null +++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CoreAndroid.java @@ -0,0 +1,390 @@ +/* + 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.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.telephony.TelephonyManager; +import android.view.KeyEvent; + +import java.lang.reflect.Field; +import java.util.HashMap; + +/** + * This class exposes methods in Cordova that can be called from JavaScript. + */ +public class CoreAndroid extends CordovaPlugin { + + public static final String PLUGIN_NAME = "CoreAndroid"; + 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. + * + * @param action The name of the event to be fired + */ + public void fireJavascriptEvent(String action) { + sendEventMessage(action); + } + + /** + * Sets the context of the Command. This can then be used to do things like + * get file paths associated with the Activity. + */ + @Override + public void pluginInitialize() { + this.initTelephonyReceiver(); + } + + /** + * Executes the request and returns PluginResult. + * + * @param action The action to execute. + * @param args JSONArry of arguments for the plugin. + * @param callbackContext The callback context from which we were invoked. + * @return A PluginResult object with a status and message. + */ + public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { + PluginResult.Status status = PluginResult.Status.OK; + String result = ""; + + try { + if (action.equals("clearCache")) { + this.clearCache(); + } + else if (action.equals("show")) { + // This gets called from JavaScript onCordovaReady to show the webview. + // I recommend we change the name of the Message as spinner/stop is not + // indicative of what this actually does (shows the webview). + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + webView.getPluginManager().postMessage("spinner", "stop"); + } + }); + } + else if (action.equals("loadUrl")) { + this.loadUrl(args.getString(0), args.optJSONObject(1)); + } + else if (action.equals("cancelLoadUrl")) { + //this.cancelLoadUrl(); + } + else if (action.equals("clearHistory")) { + this.clearHistory(); + } + else if (action.equals("backHistory")) { + this.backHistory(); + } + else if (action.equals("overrideButton")) { + this.overrideButton(args.getString(0), args.getBoolean(1)); + } + else if (action.equals("overrideBackbutton")) { + this.overrideBackbutton(args.getBoolean(0)); + } + else if (action.equals("exitApp")) { + this.exitApp(); + } + else if (action.equals("messageChannel")) { + synchronized(messageChannelLock) { + messageChannel = callbackContext; + if (pendingResume != null) { + sendEventMessage(pendingResume); + pendingResume = null; + } + } + return true; + } + + callbackContext.sendPluginResult(new PluginResult(status, result)); + return true; + } catch (JSONException e) { + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); + return false; + } + } + + //-------------------------------------------------------------------------- + // LOCAL METHODS + //-------------------------------------------------------------------------- + + /** + * Clear the resource cache. + */ + public void clearCache() { + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + webView.clearCache(true); + } + }); + } + + /** + * Load the url into the webview. + * + * @param url + * @param props Properties that can be passed in to the Cordova activity (i.e. loadingDialog, wait, ...) + * @throws JSONException + */ + public void loadUrl(String url, JSONObject props) throws JSONException { + LOG.d("App", "App.loadUrl("+url+","+props+")"); + int wait = 0; + boolean openExternal = false; + boolean clearHistory = false; + + // If there are properties, then set them on the Activity + HashMap<String, Object> params = new HashMap<String, Object>(); + if (props != null) { + JSONArray keys = props.names(); + for (int i = 0; i < keys.length(); i++) { + String key = keys.getString(i); + if (key.equals("wait")) { + wait = props.getInt(key); + } + else if (key.equalsIgnoreCase("openexternal")) { + openExternal = props.getBoolean(key); + } + else if (key.equalsIgnoreCase("clearhistory")) { + clearHistory = props.getBoolean(key); + } + else { + Object value = props.get(key); + if (value == null) { + + } + else if (value.getClass().equals(String.class)) { + params.put(key, (String)value); + } + else if (value.getClass().equals(Boolean.class)) { + params.put(key, (Boolean)value); + } + else if (value.getClass().equals(Integer.class)) { + params.put(key, (Integer)value); + } + } + } + } + + // If wait property, then delay loading + + if (wait > 0) { + try { + synchronized(this) { + this.wait(wait); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + this.webView.showWebPage(url, openExternal, clearHistory, params); + } + + /** + * Clear page history for the app. + */ + public void clearHistory() { + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + webView.clearHistory(); + } + }); + } + + /** + * Go to previous page displayed. + * This is the same as pressing the backbutton on Android device. + */ + public void backHistory() { + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + webView.backHistory(); + } + }); + } + + /** + * Override the default behavior of the Android back button. + * If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired. + * + * @param override T=override, F=cancel override + */ + public void overrideBackbutton(boolean override) { + LOG.i("App", "WARNING: Back Button Default Behavior will be overridden. The backbutton event will be fired!"); + webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_BACK, override); + } + + /** + * Override the default behavior of the Android volume buttons. + * If overridden, when the volume button is pressed, the "volume[up|down]button" JavaScript event will be fired. + * + * @param button volumeup, volumedown + * @param override T=override, F=cancel override + */ + public void overrideButton(String button, boolean override) { + LOG.i("App", "WARNING: Volume Button Default Behavior will be overridden. The volume event will be fired!"); + if (button.equals("volumeup")) { + webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_UP, override); + } + else if (button.equals("volumedown")) { + webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_DOWN, override); + } + else if (button.equals("menubutton")) { + webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_MENU, override); + } + } + + /** + * Return whether the Android back button is overridden by the user. + * + * @return boolean + */ + public boolean isBackbuttonOverridden() { + return webView.isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK); + } + + /** + * Exit the Android application. + */ + public void exitApp() { + this.webView.getPluginManager().postMessage("exit", null); + } + + + /** + * Listen for telephony events: RINGING, OFFHOOK and IDLE + * Send these events to all plugins using + * CordovaActivity.onMessage("telephone", "ringing" | "offhook" | "idle") + */ + private void initTelephonyReceiver() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); + //final CordovaInterface mycordova = this.cordova; + this.telephonyReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + + // If state has changed + if ((intent != null) && intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) { + if (intent.hasExtra(TelephonyManager.EXTRA_STATE)) { + String extraData = intent.getStringExtra(TelephonyManager.EXTRA_STATE); + if (extraData.equals(TelephonyManager.EXTRA_STATE_RINGING)) { + LOG.i(TAG, "Telephone RINGING"); + webView.getPluginManager().postMessage("telephone", "ringing"); + } + else if (extraData.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) { + LOG.i(TAG, "Telephone OFFHOOK"); + webView.getPluginManager().postMessage("telephone", "offhook"); + } + else if (extraData.equals(TelephonyManager.EXTRA_STATE_IDLE)) { + LOG.i(TAG, "Telephone IDLE"); + webView.getPluginManager().postMessage("telephone", "idle"); + } + } + } + } + }; + + // Register the receiver + webView.getContext().registerReceiver(this.telephonyReceiver, intentFilter); + } + + private void sendEventMessage(String action) { + JSONObject obj = new JSONObject(); + try { + obj.put("action", action); + } catch (JSONException e) { + LOG.e(TAG, "Failed to create event message", e); + } + sendEventMessage(new PluginResult(PluginResult.Status.OK, obj)); + } + + private void sendEventMessage(PluginResult payload) { + payload.setKeepCallback(true); + if (messageChannel != null) { + messageChannel.sendPluginResult(payload); + } + } + + /* + * Unregister the receiver + * + */ + 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; + } + } + } + + /* + * This needs to be implemented if you wish to use the Camera Plugin or other plugins + * that read the Build Configuration. + * + * Thanks to Phil@Medtronic and Graham Borland for finding the answer and posting it to + * StackOverflow. This is annoying as hell! + * + */ + + public static Object getBuildConfigValue(Context ctx, String key) + { + try + { + Class<?> clazz = Class.forName(ctx.getPackageName() + ".BuildConfig"); + Field field = clazz.getField(key); + return field.get(null); + } catch (ClassNotFoundException e) { + LOG.d(TAG, "Unable to get the BuildConfig, is this built with ANT?"); + e.printStackTrace(); + } catch (NoSuchFieldException e) { + LOG.d(TAG, key + " is not a valid field. Check your build.gradle"); + } catch (IllegalAccessException e) { + LOG.d(TAG, "Illegal Access Exception: Let's print a stack trace."); + e.printStackTrace(); + } + + return null; + } +} http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/ExposedJsApi.java ---------------------------------------------------------------------- diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/ExposedJsApi.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/ExposedJsApi.java new file mode 100644 index 0000000..acc65c6 --- /dev/null +++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/ExposedJsApi.java @@ -0,0 +1,31 @@ +/* + 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; + +/* + * Any exposed Javascript API MUST implement these three things! + */ +public interface ExposedJsApi { + public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException; + public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException; + public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException; +} http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/ICordovaClientCertRequest.java ---------------------------------------------------------------------- diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/ICordovaClientCertRequest.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/ICordovaClientCertRequest.java new file mode 100644 index 0000000..455d2f9 --- /dev/null +++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/ICordovaClientCertRequest.java @@ -0,0 +1,66 @@ +/* + 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 java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +/** + * Specifies interface for handling certificate requests. + */ +public interface ICordovaClientCertRequest { + /** + * Cancel this request + */ + public void cancel(); + + /* + * Returns the host name of the server requesting the certificate. + */ + public String getHost(); + + /* + * Returns the acceptable types of asymmetric keys (can be null). + */ + public String[] getKeyTypes(); + + /* + * Returns the port number of the server requesting the certificate. + */ + public int getPort(); + + /* + * Returns the acceptable certificate issuers for the certificate matching the private key (can be null). + */ + public Principal[] getPrincipals(); + + /* + * Ignore the request for now. Do not remember user's choice. + */ + public void ignore(); + + /* + * Proceed with the specified private key and client certificate chain. Remember the user's positive choice and use it for future requests. + * + * @param privateKey The privateKey + * @param chain The certificate chain + */ + public void proceed(PrivateKey privateKey, X509Certificate[] chain); +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/ICordovaCookieManager.java ---------------------------------------------------------------------- diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/ICordovaCookieManager.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/ICordovaCookieManager.java new file mode 100644 index 0000000..e776194 --- /dev/null +++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/ICordovaCookieManager.java @@ -0,0 +1,33 @@ +/* + 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; + +public interface ICordovaCookieManager { + + public void setCookiesEnabled(boolean accept); + + public void setCookie(final String url, final String value); + + public String getCookie(final String url); + + public void clearCookies(); + + public void flush(); +}; http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/ICordovaHttpAuthHandler.java ---------------------------------------------------------------------- diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/ICordovaHttpAuthHandler.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/ICordovaHttpAuthHandler.java new file mode 100644 index 0000000..c55818a --- /dev/null +++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/ICordovaHttpAuthHandler.java @@ -0,0 +1,38 @@ +/* + 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; + +/** + * Specifies interface for HTTP auth handler object which is used to handle auth requests and + * specifying user credentials. + */ + public interface ICordovaHttpAuthHandler { + /** + * Instructs the WebView to cancel the authentication request. + */ + public void cancel (); + + /** + * Instructs the WebView to proceed with the authentication with the given credentials. + * + * @param username The user name + * @param password The password + */ + public void proceed (String username, String password); +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/LOG.java ---------------------------------------------------------------------- diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/LOG.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/LOG.java new file mode 100755 index 0000000..9fe7a7d --- /dev/null +++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/LOG.java @@ -0,0 +1,244 @@ +/* + 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 android.util.Log; + +/** + * Log to Android logging system. + * + * Log message can be a string or a printf formatted string with arguments. + * See http://developer.android.com/reference/java/util/Formatter.html + */ +public class LOG { + + public static final int VERBOSE = Log.VERBOSE; + public static final int DEBUG = Log.DEBUG; + public static final int INFO = Log.INFO; + public static final int WARN = Log.WARN; + public static final int ERROR = Log.ERROR; + + // Current log level + public static int LOGLEVEL = Log.ERROR; + + /** + * Set the current log level. + * + * @param logLevel + */ + public static void setLogLevel(int logLevel) { + LOGLEVEL = logLevel; + Log.i("CordovaLog", "Changing log level to " + logLevel); + } + + /** + * Set the current log level. + * + * @param logLevel + */ + public static void setLogLevel(String logLevel) { + if ("VERBOSE".equals(logLevel)) LOGLEVEL = VERBOSE; + else if ("DEBUG".equals(logLevel)) LOGLEVEL = DEBUG; + else if ("INFO".equals(logLevel)) LOGLEVEL = INFO; + else if ("WARN".equals(logLevel)) LOGLEVEL = WARN; + else if ("ERROR".equals(logLevel)) LOGLEVEL = ERROR; + Log.i("CordovaLog", "Changing log level to " + logLevel + "(" + LOGLEVEL + ")"); + } + + /** + * Determine if log level will be logged + * + * @param logLevel + * @return true if the parameter passed in is greater than or equal to the current log level + */ + public static boolean isLoggable(int logLevel) { + return (logLevel >= LOGLEVEL); + } + + /** + * Verbose log message. + * + * @param tag + * @param s + */ + public static void v(String tag, String s) { + if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, s); + } + + /** + * Debug log message. + * + * @param tag + * @param s + */ + public static void d(String tag, String s) { + if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, s); + } + + /** + * Info log message. + * + * @param tag + * @param s + */ + public static void i(String tag, String s) { + if (LOG.INFO >= LOGLEVEL) Log.i(tag, s); + } + + /** + * Warning log message. + * + * @param tag + * @param s + */ + public static void w(String tag, String s) { + if (LOG.WARN >= LOGLEVEL) Log.w(tag, s); + } + + /** + * Error log message. + * + * @param tag + * @param s + */ + public static void e(String tag, String s) { + if (LOG.ERROR >= LOGLEVEL) Log.e(tag, s); + } + + /** + * Verbose log message. + * + * @param tag + * @param s + * @param e + */ + public static void v(String tag, String s, Throwable e) { + if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, s, e); + } + + /** + * Debug log message. + * + * @param tag + * @param s + * @param e + */ + public static void d(String tag, String s, Throwable e) { + if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, s, e); + } + + /** + * Info log message. + * + * @param tag + * @param s + * @param e + */ + public static void i(String tag, String s, Throwable e) { + if (LOG.INFO >= LOGLEVEL) Log.i(tag, s, e); + } + + /** + * Warning log message. + * + * @param tag + * @param e + */ + public static void w(String tag, Throwable e) { + if (LOG.WARN >= LOGLEVEL) Log.w(tag, e); + } + + /** + * Warning log message. + * + * @param tag + * @param s + * @param e + */ + public static void w(String tag, String s, Throwable e) { + if (LOG.WARN >= LOGLEVEL) Log.w(tag, s, e); + } + + /** + * Error log message. + * + * @param tag + * @param s + * @param e + */ + public static void e(String tag, String s, Throwable e) { + if (LOG.ERROR >= LOGLEVEL) Log.e(tag, s, e); + } + + /** + * Verbose log message with printf formatting. + * + * @param tag + * @param s + * @param args + */ + public static void v(String tag, String s, Object... args) { + if (LOG.VERBOSE >= LOGLEVEL) Log.v(tag, String.format(s, args)); + } + + /** + * Debug log message with printf formatting. + * + * @param tag + * @param s + * @param args + */ + public static void d(String tag, String s, Object... args) { + if (LOG.DEBUG >= LOGLEVEL) Log.d(tag, String.format(s, args)); + } + + /** + * Info log message with printf formatting. + * + * @param tag + * @param s + * @param args + */ + public static void i(String tag, String s, Object... args) { + if (LOG.INFO >= LOGLEVEL) Log.i(tag, String.format(s, args)); + } + + /** + * Warning log message with printf formatting. + * + * @param tag + * @param s + * @param args + */ + public static void w(String tag, String s, Object... args) { + if (LOG.WARN >= LOGLEVEL) Log.w(tag, String.format(s, args)); + } + + /** + * Error log message with printf formatting. + * + * @param tag + * @param s + * @param args + */ + public static void e(String tag, String s, Object... args) { + if (LOG.ERROR >= LOGLEVEL) Log.e(tag, String.format(s, args)); + } + +} http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/NativeToJsMessageQueue.java ---------------------------------------------------------------------- diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/NativeToJsMessageQueue.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/NativeToJsMessageQueue.java new file mode 100755 index 0000000..a8f59fa --- /dev/null +++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/NativeToJsMessageQueue.java @@ -0,0 +1,539 @@ +/* + 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 java.util.ArrayList; +import java.util.LinkedList; + +/** + * Holds the list of messages to be sent to the WebView. + */ +public class NativeToJsMessageQueue { + private static final String LOG_TAG = "JsMessageQueue"; + + // Set this to true to force plugin results to be encoding as + // JS instead of the custom format (useful for benchmarking). + // Doesn't work for multipart messages. + private static final boolean FORCE_ENCODE_USING_EVAL = false; + + // Disable sending back native->JS messages during an exec() when the active + // exec() is asynchronous. Set this to true when running bridge benchmarks. + static final boolean DISABLE_EXEC_CHAINING = false; + + // Arbitrarily chosen upper limit for how much data to send to JS in one shot. + // This currently only chops up on message boundaries. It may be useful + // to allow it to break up messages. + private static int MAX_PAYLOAD_SIZE = 50 * 1024 * 10240; + + /** + * When true, the active listener is not fired upon enqueue. When set to false, + * the active listener will be fired if the queue is non-empty. + */ + private boolean paused; + + /** + * The list of JavaScript statements to be sent to JavaScript. + */ + private final LinkedList<JsMessage> queue = new LinkedList<JsMessage>(); + + /** + * The array of listeners that can be used to send messages to JS. + */ + private ArrayList<BridgeMode> bridgeModes = new ArrayList<BridgeMode>(); + + /** + * When null, the bridge is disabled. This occurs during page transitions. + * When disabled, all callbacks are dropped since they are assumed to be + * relevant to the previous page. + */ + private BridgeMode activeBridgeMode; + + public void addBridgeMode(BridgeMode bridgeMode) { + bridgeModes.add(bridgeMode); + } + + public boolean isBridgeEnabled() { + return activeBridgeMode != null; + } + + public boolean isEmpty() { + return queue.isEmpty(); + } + + /** + * Changes the bridge mode. + */ + public void setBridgeMode(int value) { + if (value < -1 || value >= bridgeModes.size()) { + LOG.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value); + } else { + BridgeMode newMode = value < 0 ? null : bridgeModes.get(value); + if (newMode != activeBridgeMode) { + LOG.d(LOG_TAG, "Set native->JS mode to " + (newMode == null ? "null" : newMode.getClass().getSimpleName())); + synchronized (this) { + activeBridgeMode = newMode; + if (newMode != null) { + newMode.reset(); + if (!paused && !queue.isEmpty()) { + newMode.onNativeToJsMessageAvailable(this); + } + } + } + } + } + } + + /** + * Clears all messages and resets to the default bridge mode. + */ + public void reset() { + synchronized (this) { + queue.clear(); + setBridgeMode(-1); + } + } + + private int calculatePackedMessageLength(JsMessage message) { + int messageLen = message.calculateEncodedLength(); + String messageLenStr = String.valueOf(messageLen); + return messageLenStr.length() + messageLen + 1; + } + + private void packMessage(JsMessage message, StringBuilder sb) { + int len = message.calculateEncodedLength(); + sb.append(len) + .append(' '); + message.encodeAsMessage(sb); + } + + /** + * Combines and returns queued messages combined into a single string. + * Combines as many messages as possible, while staying under MAX_PAYLOAD_SIZE. + * Returns null if the queue is empty. + */ + public String popAndEncode(boolean fromOnlineEvent) { + synchronized (this) { + if (activeBridgeMode == null) { + return null; + } + activeBridgeMode.notifyOfFlush(this, fromOnlineEvent); + if (queue.isEmpty()) { + return null; + } + int totalPayloadLen = 0; + int numMessagesToSend = 0; + for (JsMessage message : queue) { + int messageSize = calculatePackedMessageLength(message); + if (numMessagesToSend > 0 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE && MAX_PAYLOAD_SIZE > 0) { + break; + } + totalPayloadLen += messageSize; + numMessagesToSend += 1; + } + + StringBuilder sb = new StringBuilder(totalPayloadLen); + for (int i = 0; i < numMessagesToSend; ++i) { + JsMessage message = queue.removeFirst(); + packMessage(message, sb); + } + + if (!queue.isEmpty()) { + // Attach a char to indicate that there are more messages pending. + sb.append('*'); + } + String ret = sb.toString(); + return ret; + } + } + + /** + * Same as popAndEncode(), except encodes in a form that can be executed as JS. + */ + public String popAndEncodeAsJs() { + synchronized (this) { + int length = queue.size(); + if (length == 0) { + return null; + } + int totalPayloadLen = 0; + int numMessagesToSend = 0; + for (JsMessage message : queue) { + int messageSize = message.calculateEncodedLength() + 50; // overestimate. + if (numMessagesToSend > 0 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE && MAX_PAYLOAD_SIZE > 0) { + break; + } + totalPayloadLen += messageSize; + numMessagesToSend += 1; + } + boolean willSendAllMessages = numMessagesToSend == queue.size(); + StringBuilder sb = new StringBuilder(totalPayloadLen + (willSendAllMessages ? 0 : 100)); + // Wrap each statement in a try/finally so that if one throws it does + // not affect the next. + for (int i = 0; i < numMessagesToSend; ++i) { + JsMessage message = queue.removeFirst(); + if (willSendAllMessages && (i + 1 == numMessagesToSend)) { + message.encodeAsJsMessage(sb); + } else { + sb.append("try{"); + message.encodeAsJsMessage(sb); + sb.append("}finally{"); + } + } + if (!willSendAllMessages) { + sb.append("window.setTimeout(function(){cordova.require('cordova/plugin/android/polling').pollOnce();},0);"); + } + for (int i = willSendAllMessages ? 1 : 0; i < numMessagesToSend; ++i) { + sb.append('}'); + } + String ret = sb.toString(); + return ret; + } + } + + /** + * Add a JavaScript statement to the list. + */ + public void addJavaScript(String statement) { + enqueueMessage(new JsMessage(statement)); + } + + /** + * Add a JavaScript statement to the list. + */ + public void addPluginResult(PluginResult result, String callbackId) { + if (callbackId == null) { + LOG.e(LOG_TAG, "Got plugin result with no callbackId", new Throwable()); + return; + } + // Don't send anything if there is no result and there is no need to + // clear the callbacks. + boolean noResult = result.getStatus() == PluginResult.Status.NO_RESULT.ordinal(); + boolean keepCallback = result.getKeepCallback(); + if (noResult && keepCallback) { + return; + } + JsMessage message = new JsMessage(result, callbackId); + if (FORCE_ENCODE_USING_EVAL) { + StringBuilder sb = new StringBuilder(message.calculateEncodedLength() + 50); + message.encodeAsJsMessage(sb); + message = new JsMessage(sb.toString()); + } + + enqueueMessage(message); + } + + private void enqueueMessage(JsMessage message) { + synchronized (this) { + if (activeBridgeMode == null) { + LOG.d(LOG_TAG, "Dropping Native->JS message due to disabled bridge"); + return; + } + queue.add(message); + if (!paused) { + activeBridgeMode.onNativeToJsMessageAvailable(this); + } + } + } + + public void setPaused(boolean value) { + if (paused && value) { + // This should never happen. If a use-case for it comes up, we should + // change pause to be a counter. + LOG.e(LOG_TAG, "nested call to setPaused detected.", new Throwable()); + } + paused = value; + if (!value) { + synchronized (this) { + if (!queue.isEmpty() && activeBridgeMode != null) { + activeBridgeMode.onNativeToJsMessageAvailable(this); + } + } + } + } + + public static abstract class BridgeMode { + public abstract void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue); + public void notifyOfFlush(NativeToJsMessageQueue queue, boolean fromOnlineEvent) {} + public void reset() {} + } + + /** Uses JS polls for messages on a timer.. */ + public static class NoOpBridgeMode extends BridgeMode { + @Override public void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue) { + } + } + + /** Uses webView.loadUrl("javascript:") to execute messages. */ + public static class LoadUrlBridgeMode extends BridgeMode { + private final CordovaWebViewEngine engine; + private final CordovaInterface cordova; + + public LoadUrlBridgeMode(CordovaWebViewEngine engine, CordovaInterface cordova) { + this.engine = engine; + this.cordova = cordova; + } + + @Override + public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) { + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + String js = queue.popAndEncodeAsJs(); + if (js != null) { + engine.loadUrl("javascript:" + js, false); + } + } + }); + } + } + + /** Uses online/offline events to tell the JS when to poll for messages. */ + public static class OnlineEventsBridgeMode extends BridgeMode { + private final OnlineEventsBridgeModeDelegate delegate; + private boolean online; + private boolean ignoreNextFlush; + + public interface OnlineEventsBridgeModeDelegate { + void setNetworkAvailable(boolean value); + void runOnUiThread(Runnable r); + } + + public OnlineEventsBridgeMode(OnlineEventsBridgeModeDelegate delegate) { + this.delegate = delegate; + } + + @Override + public void reset() { + delegate.runOnUiThread(new Runnable() { + public void run() { + online = false; + // If the following call triggers a notifyOfFlush, then ignore it. + ignoreNextFlush = true; + delegate.setNetworkAvailable(true); + } + }); + } + + @Override + public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) { + delegate.runOnUiThread(new Runnable() { + public void run() { + if (!queue.isEmpty()) { + ignoreNextFlush = false; + delegate.setNetworkAvailable(online); + } + } + }); + } + // Track when online/offline events are fired so that we don't fire excess events. + @Override + public void notifyOfFlush(final NativeToJsMessageQueue queue, boolean fromOnlineEvent) { + if (fromOnlineEvent && !ignoreNextFlush) { + online = !online; + } + } + } + + /** Uses webView.evaluateJavascript to execute messages. */ + public static class EvalBridgeMode extends BridgeMode { + private final CordovaWebViewEngine engine; + private final CordovaInterface cordova; + + public EvalBridgeMode(CordovaWebViewEngine engine, CordovaInterface cordova) { + this.engine = engine; + this.cordova = cordova; + } + + @Override + public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) { + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + String js = queue.popAndEncodeAsJs(); + if (js != null) { + engine.evaluateJavascript(js, null); + } + } + }); + } + } + + + + private static class JsMessage { + final String jsPayloadOrCallbackId; + final PluginResult pluginResult; + JsMessage(String js) { + if (js == null) { + throw new NullPointerException(); + } + jsPayloadOrCallbackId = js; + pluginResult = null; + } + JsMessage(PluginResult pluginResult, String callbackId) { + if (callbackId == null || pluginResult == null) { + throw new NullPointerException(); + } + jsPayloadOrCallbackId = callbackId; + this.pluginResult = pluginResult; + } + + static int calculateEncodedLengthHelper(PluginResult pluginResult) { + switch (pluginResult.getMessageType()) { + case PluginResult.MESSAGE_TYPE_BOOLEAN: // f or t + case PluginResult.MESSAGE_TYPE_NULL: // N + return 1; + case PluginResult.MESSAGE_TYPE_NUMBER: // n + return 1 + pluginResult.getMessage().length(); + case PluginResult.MESSAGE_TYPE_STRING: // s + return 1 + pluginResult.getStrMessage().length(); + case PluginResult.MESSAGE_TYPE_BINARYSTRING: + return 1 + pluginResult.getMessage().length(); + case PluginResult.MESSAGE_TYPE_ARRAYBUFFER: + return 1 + pluginResult.getMessage().length(); + case PluginResult.MESSAGE_TYPE_MULTIPART: + int ret = 1; + for (int i = 0; i < pluginResult.getMultipartMessagesSize(); i++) { + int length = calculateEncodedLengthHelper(pluginResult.getMultipartMessage(i)); + int argLength = String.valueOf(length).length(); + ret += argLength + 1 + length; + } + return ret; + case PluginResult.MESSAGE_TYPE_JSON: + default: + return pluginResult.getMessage().length(); + } + } + + int calculateEncodedLength() { + if (pluginResult == null) { + return jsPayloadOrCallbackId.length() + 1; + } + int statusLen = String.valueOf(pluginResult.getStatus()).length(); + int ret = 2 + statusLen + 1 + jsPayloadOrCallbackId.length() + 1; + return ret + calculateEncodedLengthHelper(pluginResult); + } + + static void encodeAsMessageHelper(StringBuilder sb, PluginResult pluginResult) { + switch (pluginResult.getMessageType()) { + case PluginResult.MESSAGE_TYPE_BOOLEAN: + sb.append(pluginResult.getMessage().charAt(0)); // t or f. + break; + case PluginResult.MESSAGE_TYPE_NULL: // N + sb.append('N'); + break; + case PluginResult.MESSAGE_TYPE_NUMBER: // n + sb.append('n') + .append(pluginResult.getMessage()); + break; + case PluginResult.MESSAGE_TYPE_STRING: // s + sb.append('s'); + sb.append(pluginResult.getStrMessage()); + break; + case PluginResult.MESSAGE_TYPE_BINARYSTRING: // S + sb.append('S'); + sb.append(pluginResult.getMessage()); + break; + case PluginResult.MESSAGE_TYPE_ARRAYBUFFER: // A + sb.append('A'); + sb.append(pluginResult.getMessage()); + break; + case PluginResult.MESSAGE_TYPE_MULTIPART: + sb.append('M'); + for (int i = 0; i < pluginResult.getMultipartMessagesSize(); i++) { + PluginResult multipartMessage = pluginResult.getMultipartMessage(i); + sb.append(String.valueOf(calculateEncodedLengthHelper(multipartMessage))); + sb.append(' '); + encodeAsMessageHelper(sb, multipartMessage); + } + break; + case PluginResult.MESSAGE_TYPE_JSON: + default: + sb.append(pluginResult.getMessage()); // [ or { + } + } + + void encodeAsMessage(StringBuilder sb) { + if (pluginResult == null) { + sb.append('J') + .append(jsPayloadOrCallbackId); + return; + } + int status = pluginResult.getStatus(); + boolean noResult = status == PluginResult.Status.NO_RESULT.ordinal(); + boolean resultOk = status == PluginResult.Status.OK.ordinal(); + boolean keepCallback = pluginResult.getKeepCallback(); + + sb.append((noResult || resultOk) ? 'S' : 'F') + .append(keepCallback ? '1' : '0') + .append(status) + .append(' ') + .append(jsPayloadOrCallbackId) + .append(' '); + + encodeAsMessageHelper(sb, pluginResult); + } + + void buildJsMessage(StringBuilder sb) { + switch (pluginResult.getMessageType()) { + case PluginResult.MESSAGE_TYPE_MULTIPART: + int size = pluginResult.getMultipartMessagesSize(); + for (int i=0; i<size; i++) { + PluginResult subresult = pluginResult.getMultipartMessage(i); + JsMessage submessage = new JsMessage(subresult, jsPayloadOrCallbackId); + submessage.buildJsMessage(sb); + if (i < (size-1)) { + sb.append(","); + } + } + break; + case PluginResult.MESSAGE_TYPE_BINARYSTRING: + sb.append("atob('") + .append(pluginResult.getMessage()) + .append("')"); + break; + case PluginResult.MESSAGE_TYPE_ARRAYBUFFER: + sb.append("cordova.require('cordova/base64').toArrayBuffer('") + .append(pluginResult.getMessage()) + .append("')"); + break; + default: + sb.append(pluginResult.getMessage()); + } + } + + void encodeAsJsMessage(StringBuilder sb) { + if (pluginResult == null) { + sb.append(jsPayloadOrCallbackId); + } else { + int status = pluginResult.getStatus(); + boolean success = (status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal()); + sb.append("cordova.callbackFromNative('") + .append(jsPayloadOrCallbackId) + .append("',") + .append(success) + .append(",") + .append(status) + .append(",["); + buildJsMessage(sb); + sb.append("],") + .append(pluginResult.getKeepCallback()) + .append(");"); + } + } + } +} http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/PluginEntry.java ---------------------------------------------------------------------- diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/PluginEntry.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/PluginEntry.java new file mode 100755 index 0000000..c56c453 --- /dev/null +++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/PluginEntry.java @@ -0,0 +1,70 @@ +/* + 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.apache.cordova.CordovaPlugin; + +/** + * This class represents a service entry object. + */ +public final class PluginEntry { + + /** + * The name of the service that this plugin implements + */ + public final String service; + + /** + * The plugin class name that implements the service. + */ + public final String pluginClass; + + /** + * The pre-instantiated plugin to use for this entry. + */ + public final CordovaPlugin plugin; + + /** + * Flag that indicates the plugin object should be created when PluginManager is initialized. + */ + public final boolean onload; + + /** + * Constructs with a CordovaPlugin already instantiated. + */ + public PluginEntry(String service, CordovaPlugin plugin) { + this(service, plugin.getClass().getName(), true, plugin); + } + + /** + * @param service The name of the service + * @param pluginClass The plugin class name + * @param onload Create plugin object when HTML page is loaded + */ + public PluginEntry(String service, String pluginClass, boolean onload) { + this(service, pluginClass, onload, null); + } + + private PluginEntry(String service, String pluginClass, boolean onload, CordovaPlugin plugin) { + this.service = service; + this.pluginClass = pluginClass; + this.onload = onload; + this.plugin = plugin; + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cordova.apache.org For additional commands, e-mail: commits-h...@cordova.apache.org