Make work with the latest changes to android-4.0.x
Project: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/commit/f30b3bc6 Tree: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/tree/f30b3bc6 Diff: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/diff/f30b3bc6 Branch: refs/heads/master Commit: f30b3bc6d67067ad1eb9cf1e5c70231565c4c2f8 Parents: 7cf88fe Author: Andrew Grieve <[email protected]> Authored: Tue Jul 8 22:27:57 2014 -0400 Committer: Andrew Grieve <[email protected]> Committed: Tue Jul 8 22:27:57 2014 -0400 ---------------------------------------------------------------------- AppHarnessUI/AppHarnessUI.java | 307 ------------------- AppHarnessUI/android/AppHarnessUI.java | 231 ++++++++++++++ AppHarnessUI/android/CordovaAppHarness.java | 65 ++++ AppHarnessUI/android/CustomAndroidWebView.java | 90 ++++++ AppHarnessUI/android/CustomCordovaWebView.java | 26 ++ .../TwoFingerDoubleTapGestureDetector.java | 68 ++++ AppHarnessUI/plugin.xml | 5 +- template-overrides/Activity.java | 4 +- 8 files changed, 486 insertions(+), 310 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f30b3bc6/AppHarnessUI/AppHarnessUI.java ---------------------------------------------------------------------- diff --git a/AppHarnessUI/AppHarnessUI.java b/AppHarnessUI/AppHarnessUI.java deleted file mode 100644 index fd64295..0000000 --- a/AppHarnessUI/AppHarnessUI.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - 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.appharness; - -import java.util.HashSet; -import java.util.Set; - -import org.apache.cordova.AndroidChromeClient; -import org.apache.cordova.AndroidWebView; -import org.apache.cordova.CallbackContext; -import org.apache.cordova.CordovaActivity; -import org.apache.cordova.CordovaArgs; -import org.apache.cordova.CordovaChromeClient; -import org.apache.cordova.CordovaPlugin; -import org.apache.cordova.CordovaWebViewClient; -import org.apache.cordova.IceCreamCordovaWebViewClient; -import org.apache.cordova.LinearLayoutSoftKeyboardDetect; -import org.apache.cordova.PluginResult; -import org.json.JSONArray; -import org.json.JSONException; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Context; -import android.os.Build; -import android.util.Log; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.ViewPropertyAnimator; -import android.view.animation.DecelerateInterpolator; -import android.widget.FrameLayout; -import android.widget.LinearLayout; - -public class AppHarnessUI extends CordovaPlugin { - private static final String LOG_TAG = "AppHarnessUI"; - ViewGroup contentView; - View origMainView; - CustomCordovaWebView slaveWebView; - boolean slaveVisible; - CallbackContext eventsCallback; - - public boolean isSlaveVisible() { - return slaveVisible; - } - - public boolean isSlaveCreated() { - return slaveWebView != null && slaveWebView.getParent() != null; - } - - @Override - public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException { - if ("create".equals(action)) { - final String url = args.getString(0); - JSONArray pluginIdWhitelist = args.getJSONArray(1); - final Set<String> pluginIdWhitelistAsSet = new HashSet<String>(pluginIdWhitelist.length()); - for (int i = 0; i < pluginIdWhitelist.length(); ++i) { - pluginIdWhitelistAsSet.add(pluginIdWhitelist.getString(i)); - } - this.cordova.getActivity().runOnUiThread(new Runnable() { - public void run() { - create(url, pluginIdWhitelistAsSet, callbackContext); - } - }); - } else if ("destroy".equals(action)) { - this.cordova.getActivity().runOnUiThread(new Runnable() { - public void run() { - destroy(callbackContext); - } - }); - } else if ("setVisible".equals(action)) { - final boolean value = args.getBoolean(0); - this.cordova.getActivity().runOnUiThread(new Runnable() { - public void run() { - setSlaveVisible(value, callbackContext); - } - }); - } else if ("evalJs".equals(action)) { - final String code = args.getString(0); - this.cordova.getActivity().runOnUiThread(new Runnable() { - public void run() { - evalJs(code, callbackContext); - } - }); - } else if ("events".equals(action)) { - eventsCallback = callbackContext; - } else { - return false; - } - return true; - } - - public void sendEvent(String eventName) { - if (eventsCallback != null) { - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, eventName); - pluginResult.setKeepCallback(true); - eventsCallback.sendPluginResult(pluginResult ); - } - } - - @SuppressLint("NewApi") - private void evalJs(String code, CallbackContext callbackContext) { - if (slaveWebView == null) { - Log.w(LOG_TAG, "Not evaluating JS since no app is active"); - } else { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - slaveWebView.loadUrl("javascript:" + code); - } else { - slaveWebView.evaluateJavascript(code, null); - } - } - callbackContext.success(); - } - - private void create(String url, Set<String> pluginIdWhitelist, CallbackContext callbackContext) { - CordovaActivity activity = (CordovaActivity)cordova.getActivity(); - - if (slaveWebView != null) { - Log.w(LOG_TAG, "create: already exists"); - } else { - slaveWebView = new CustomCordovaWebView(activity); - initWebView(slaveWebView); - if (activity.getBooleanProperty("DisallowOverscroll", false)) { - slaveWebView.setOverScrollMode(View.OVER_SCROLL_NEVER); - } - slaveWebView.clearCache(true); - slaveWebView.clearHistory(); - slaveWebView.getPluginManager().setPluginIdWhitelist(pluginIdWhitelist); - slaveWebView.loadUrl(url); - View newView = (View)slaveWebView.getParent(); - contentView.addView(newView); - slaveVisible = true; - // Back button capturing breaks without these: - newView.requestFocus(); - - } - callbackContext.success(); - } - - private void destroy(CallbackContext callbackContext) { - if (slaveWebView == null) { - Log.w(LOG_TAG, "destroy: already destroyed"); - } else { - contentView.removeView((View)slaveWebView.getParent()); - slaveWebView.destroy(); - origMainView.requestFocus(); - slaveWebView = null; - slaveVisible = false; - sendEvent("destroyed"); - } - if (eventsCallback != null) { - eventsCallback.success(""); - eventsCallback = null; - } - callbackContext.success(); - } - - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) - private void setSlaveVisible(boolean value, CallbackContext callbackContext) { - if (value == slaveVisible) { - return; - } - if (slaveWebView == null) { - Log.w(LOG_TAG, "setSlaveVisible: slave not created"); - } else { - slaveVisible = value; - ViewPropertyAnimator anim = slaveWebView.animate(); - // Note: Pivot is set in onSizeChanged. - if (value) { - anim.scaleX(1.0f).scaleY(1.0f); - ((View)slaveWebView.getParent()).requestFocus(); - } else { - anim.scaleX(.25f).scaleY(.25f); - origMainView.requestFocus(); - } - slaveWebView.stealTapEvents = !value; - anim.setDuration(300).setInterpolator(new DecelerateInterpolator(2.0f)).start(); - } - if (callbackContext != null) { - callbackContext.success(); - } - } - - private void initWebView(final AndroidWebView newWebView) { - CordovaActivity activity = (CordovaActivity)cordova.getActivity(); - if (contentView == null) { - contentView = (ViewGroup)activity.findViewById(android.R.id.content); - origMainView = contentView.getChildAt(0); - } - - LinearLayoutSoftKeyboardDetect layoutView = new LinearLayoutSoftKeyboardDetect(activity, contentView.getWidth(), contentView.getHeight()); - layoutView.setOrientation(LinearLayout.VERTICAL); - -// layoutView.setBackground(origRootView.getBackground()); - layoutView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.BOTTOM | Gravity.LEFT)); - - newWebView.setWebViewClient((CordovaWebViewClient)new IceCreamCordovaWebViewClient(cordova, newWebView)); - newWebView.setWebChromeClient((CordovaChromeClient)new AndroidChromeClient(cordova, newWebView)); - - newWebView.setLayoutParams(new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - 1.0F)); - layoutView.addView(newWebView); - } - - // Based on: http://stackoverflow.com/questions/12414680/how-to-implement-a-two-finger-double-click-in-android - private class TwoFingerDoubleTapGestureDetector { - private final int TIMEOUT = ViewConfiguration.getDoubleTapTimeout() + 100; - private long mFirstDownTime = 0; - private boolean mSeparateTouches = false; - private byte mTwoFingerTapCount = 0; - - private void reset(long time) { - mFirstDownTime = time; - mSeparateTouches = false; - mTwoFingerTapCount = 0; - } - - public boolean onTouchEvent(MotionEvent event) { - switch(event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - if(mFirstDownTime == 0 || event.getEventTime() - mFirstDownTime > TIMEOUT) - reset(event.getDownTime()); - break; - case MotionEvent.ACTION_POINTER_UP: - if(event.getPointerCount() == 2) - mTwoFingerTapCount++; - else - mFirstDownTime = 0; - break; - case MotionEvent.ACTION_UP: - if(!mSeparateTouches) - mSeparateTouches = true; - else if(mTwoFingerTapCount == 2 && event.getEventTime() - mFirstDownTime < TIMEOUT) { - sendEvent("showMenu"); - mFirstDownTime = 0; - return true; - } - } - - return false; - } - - } - - private class CustomCordovaWebView extends AndroidWebView { - TwoFingerDoubleTapGestureDetector twoFingerTapDetector; - boolean stealTapEvents; - - public CustomCordovaWebView(Context context) { - super(context); - twoFingerTapDetector = new TwoFingerDoubleTapGestureDetector(); - } - - @Override - public boolean onTouchEvent(MotionEvent e) { - if (stealTapEvents) { - if (e.getAction() == MotionEvent.ACTION_UP) { - sendEvent("hideMenu"); - } - return true; - } - twoFingerTapDetector.onTouchEvent(e); - return super.onTouchEvent(e); - } - - @SuppressLint("NewApi") - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - // Needed for the view to stay in the bottom when rotating. - setPivotY(h); - } - - @Override - public boolean backHistory() { - if (getView().getNavigationHistory().canGoBack()) { - return super.backHistory(); - } - if (slaveVisible) { - sendEvent("showMenu"); - return true; - } - // Should never get here since the webview does not have focus. - Log.w(LOG_TAG, "Somehow back button was pressed when app not visible"); - return false; - } - } -} http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f30b3bc6/AppHarnessUI/android/AppHarnessUI.java ---------------------------------------------------------------------- diff --git a/AppHarnessUI/android/AppHarnessUI.java b/AppHarnessUI/android/AppHarnessUI.java new file mode 100644 index 0000000..431f2d9 --- /dev/null +++ b/AppHarnessUI/android/AppHarnessUI.java @@ -0,0 +1,231 @@ +/* + 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.appharness; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.ConfigXmlParser; +import org.apache.cordova.CordovaActivity; +import org.apache.cordova.CordovaArgs; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.LinearLayoutSoftKeyboardDetect; +import org.apache.cordova.PluginEntry; +import org.apache.cordova.PluginResult; +import org.json.JSONArray; +import org.json.JSONException; + +import android.annotation.TargetApi; +import android.os.Build; +import android.util.Log; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; +import android.view.animation.DecelerateInterpolator; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +@TargetApi(Build.VERSION_CODES.HONEYCOMB) +public class AppHarnessUI extends CordovaPlugin { + private static final String LOG_TAG = "AppHarnessUI"; + ViewGroup contentView; + View origMainView; + CustomCordovaWebView slaveWebView; + boolean slaveVisible; + CallbackContext eventsCallback; + LinearLayoutSoftKeyboardDetect layoutView; + + public boolean isSlaveVisible() { + return slaveVisible; + } + + public boolean isSlaveCreated() { + return slaveWebView != null && slaveWebView.getView().getParent() != null && ((ViewGroup)slaveWebView.getView().getParent()).getParent() != null; + } + + @Override + public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException { + if ("create".equals(action)) { + final String url = args.getString(0); + JSONArray pluginIdWhitelist = args.getJSONArray(1); + final Set<String> pluginIdWhitelistAsSet = new HashSet<String>(pluginIdWhitelist.length()); + for (int i = 0; i < pluginIdWhitelist.length(); ++i) { + pluginIdWhitelistAsSet.add(pluginIdWhitelist.getString(i)); + } + this.cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + create(url, pluginIdWhitelistAsSet, callbackContext); + } + }); + } else if ("destroy".equals(action)) { + this.cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + destroy(callbackContext); + } + }); + } else if ("setVisible".equals(action)) { + final boolean value = args.getBoolean(0); + this.cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + setSlaveVisible(value, callbackContext); + } + }); + } else if ("evalJs".equals(action)) { + final String code = args.getString(0); + this.cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + evalJs(code, callbackContext); + } + }); + } else if ("events".equals(action)) { + eventsCallback = callbackContext; + } else { + return false; + } + return true; + } + + public void sendEvent(String eventName) { + if (eventsCallback != null) { + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, eventName); + pluginResult.setKeepCallback(true); + eventsCallback.sendPluginResult(pluginResult ); + } + } + + private void evalJs(String code, CallbackContext callbackContext) { + if (slaveWebView == null) { + Log.w(LOG_TAG, "Not evaluating JS since no app is active"); + } else { + slaveWebView.evaluateJavascript(code); + } + callbackContext.success(); + } + + private void create(String url, Set<String> pluginIdWhitelist, CallbackContext callbackContext) { + CordovaActivity activity = (CordovaActivity)cordova.getActivity(); + + if (slaveWebView != null) { + Log.w(LOG_TAG, "create: already exists"); + } else { + slaveWebView = new CustomAndroidWebView(this, activity); + } + { + initWebView(slaveWebView, pluginIdWhitelist); + if (preferences.getBoolean("DisallowOverscroll", false)) { + slaveWebView.getView().setOverScrollMode(View.OVER_SCROLL_NEVER); + } + slaveWebView.clearCache(true); + slaveWebView.clearHistory(); + slaveWebView.getPluginManager().setPluginIdWhitelist(pluginIdWhitelist); + slaveWebView.loadUrl(url); + View newView = (View)slaveWebView.getView().getParent(); + contentView.addView(newView); + slaveVisible = true; + // Back button capturing breaks without these: + webView.getView().setEnabled(false); + newView.requestFocus(); + } + callbackContext.success(); + } + + private void destroy(CallbackContext callbackContext) { + if (slaveWebView == null) { + Log.w(LOG_TAG, "destroy: already destroyed"); + } else { + slaveWebView.loadUrl("data:text/plain;charset=utf-8,"); + contentView.removeView((View)slaveWebView.getView().getParent()); + webView.getView().setEnabled(true); + origMainView.requestFocus(); + + slaveWebView.getView().setScaleX(1.0f); + slaveWebView.getView().setScaleY(1.0f); + slaveWebView.setStealTapEvents(false); + slaveVisible = false; + sendEvent("destroyed"); + } + if (eventsCallback != null) { + eventsCallback.success(""); + eventsCallback = null; + } + callbackContext.success(); + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + private void setSlaveVisible(boolean value, CallbackContext callbackContext) { + if (value == slaveVisible) { + return; + } + if (slaveWebView == null) { + Log.w(LOG_TAG, "setSlaveVisible: slave not created"); + } else { + slaveVisible = value; + ViewPropertyAnimator anim = slaveWebView.getView().animate(); + // Note: Pivot is set in onSizeChanged. + if (value) { + anim.scaleX(1.0f).scaleY(1.0f); + webView.getView().setEnabled(false); + slaveWebView.getView().setEnabled(true); + ((View)slaveWebView.getView().getParent()).requestFocus(); + } else { + anim.scaleX(.25f).scaleY(.25f); + webView.getView().setEnabled(true); + slaveWebView.getView().setEnabled(false); + origMainView.requestFocus(); + } + slaveWebView.setStealTapEvents( !value); + anim.setDuration(300).setInterpolator(new DecelerateInterpolator(2.0f)).start(); + } + if (callbackContext != null) { + callbackContext.success(); + } + } + + private void initWebView(final CustomCordovaWebView newWebView, Set<String> pluginIdWhitelist) { + CordovaActivity activity = (CordovaActivity)cordova.getActivity(); + ConfigXmlParser parser = new ConfigXmlParser(); + // TODO: Parse the app's config.xml rather than our own config.xml. + parser.parse(activity); + ArrayList<PluginEntry> pluginEntries = parser.getPluginEntries(); + + newWebView.init(cordova, pluginEntries, webView.getWhitelist(), preferences); + if (contentView == null) { + contentView = (ViewGroup)activity.findViewById(android.R.id.content); + origMainView = contentView.getChildAt(0); + } + + if(layoutView == null) { + layoutView = new LinearLayoutSoftKeyboardDetect(activity, contentView.getWidth(), contentView.getHeight()); + layoutView.addView(newWebView.getView()); + } + layoutView.setOrientation(LinearLayout.VERTICAL); + +// layoutView.setBackground(origRootView.getBackground()); + layoutView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.BOTTOM | Gravity.LEFT)); + + newWebView.getView().setLayoutParams(new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + 1.0F)); + newWebView.getView().setVisibility(View.VISIBLE); + } +} http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f30b3bc6/AppHarnessUI/android/CordovaAppHarness.java ---------------------------------------------------------------------- diff --git a/AppHarnessUI/android/CordovaAppHarness.java b/AppHarnessUI/android/CordovaAppHarness.java new file mode 100644 index 0000000..bb8b432 --- /dev/null +++ b/AppHarnessUI/android/CordovaAppHarness.java @@ -0,0 +1,65 @@ +/* + 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.appharness; + +import org.apache.appharness.AppHarnessUI; +import android.os.Bundle; +import org.apache.cordova.*; + +public class CordovaAppHarness extends CordovaActivity +{ + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + // Set by <content src="index.html" /> in config.xml + loadUrl(launchUrl); + } + + @Override + public void onBackPressed() { + // If app is running, quit it. + AppHarnessUI ahui = (AppHarnessUI)appView.getPluginManager().getPlugin("AppHarnessUI"); + if (ahui != null) { + if (ahui.isSlaveCreated()) { + ahui.sendEvent("quitApp"); + return; + } + } + // Otherwise, hide instead of calling .finish(). + moveTaskToBack(true); + } + + @Override + public Object onMessage(String id, Object data) { + // Capture the app calling navigator.app.exitApp(). + if ("exit".equals(id)) { + AppHarnessUI ahui = (AppHarnessUI)appView.getPluginManager().getPlugin("AppHarnessUI"); + if (ahui != null) { + if (ahui.isSlaveCreated()) { + ahui.sendEvent("quitApp"); + return new Object(); + } + } + } + return super.onMessage(id, data); + } + +} http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f30b3bc6/AppHarnessUI/android/CustomAndroidWebView.java ---------------------------------------------------------------------- diff --git a/AppHarnessUI/android/CustomAndroidWebView.java b/AppHarnessUI/android/CustomAndroidWebView.java new file mode 100644 index 0000000..86f616a --- /dev/null +++ b/AppHarnessUI/android/CustomAndroidWebView.java @@ -0,0 +1,90 @@ +/* + 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.appharness; + +import org.apache.cordova.AndroidWebView; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Build; +import android.util.Log; +import android.view.MotionEvent; +import android.webkit.WebView; + +class CustomAndroidWebView extends AndroidWebView implements CustomCordovaWebView { + private static final String LOG_TAG = "AppHarnessUI"; + + private AppHarnessUI parent; + + public CustomAndroidWebView(AppHarnessUI parent, Context context) { + super(context); + this.parent = parent; + twoFingerTapDetector = new TwoFingerDoubleTapGestureDetector(); + twoFingerTapDetector.setParent(parent); + } + + public void setStealTapEvents(boolean value){ + stealTapEvents=value; + } + + TwoFingerDoubleTapGestureDetector twoFingerTapDetector; + boolean stealTapEvents; + + @Override + public boolean onTouchEvent(MotionEvent e) { + if (stealTapEvents) { + if (e.getAction() == MotionEvent.ACTION_UP) { + parent.sendEvent("hideMenu"); + } + return true; + } + twoFingerTapDetector.onTouchEvent(e); + return super.onTouchEvent(e); + } + + @SuppressLint("NewApi") + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + // Needed for the view to stay in the bottom when rotating. + setPivotY(h); + } + + @SuppressLint("NewApi") + public void evaluateJavascript(String script) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + loadUrl("javascript:" + script); + } else { + ((WebView)this).evaluateJavascript(script, null); + } + } + + @Override + public boolean backHistory() { + if (canGoBack()) { + return super.backHistory(); + } + if (parent.slaveVisible) { + parent.sendEvent("showMenu"); + return true; + } + // Should never get here since the webview does not have focus. + Log.w(LOG_TAG, "Somehow back button was pressed when app not visible"); + return false; + } +} http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f30b3bc6/AppHarnessUI/android/CustomCordovaWebView.java ---------------------------------------------------------------------- diff --git a/AppHarnessUI/android/CustomCordovaWebView.java b/AppHarnessUI/android/CustomCordovaWebView.java new file mode 100644 index 0000000..f879151 --- /dev/null +++ b/AppHarnessUI/android/CustomCordovaWebView.java @@ -0,0 +1,26 @@ +/* + 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.appharness; + +import org.apache.cordova.CordovaWebView; + +interface CustomCordovaWebView extends CordovaWebView { + void setStealTapEvents(boolean value); + void evaluateJavascript(String script); +} http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f30b3bc6/AppHarnessUI/android/TwoFingerDoubleTapGestureDetector.java ---------------------------------------------------------------------- diff --git a/AppHarnessUI/android/TwoFingerDoubleTapGestureDetector.java b/AppHarnessUI/android/TwoFingerDoubleTapGestureDetector.java new file mode 100644 index 0000000..f3060c2 --- /dev/null +++ b/AppHarnessUI/android/TwoFingerDoubleTapGestureDetector.java @@ -0,0 +1,68 @@ +/* + 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.appharness; + +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +// Based on: http://stackoverflow.com/questions/12414680/how-to-implement-a-two-finger-double-click-in-android +class TwoFingerDoubleTapGestureDetector { + private AppHarnessUI parent; + + private final int TIMEOUT = ViewConfiguration.getDoubleTapTimeout() + 100; + private long mFirstDownTime = 0; + private boolean mSeparateTouches = false; + private byte mTwoFingerTapCount = 0; + + public void setParent(AppHarnessUI parent) { + this.parent = parent; + } + + private void reset(long time) { + mFirstDownTime = time; + mSeparateTouches = false; + mTwoFingerTapCount = 0; + } + + public boolean onTouchEvent(MotionEvent event) { + switch(event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + if(mFirstDownTime == 0 || event.getEventTime() - mFirstDownTime > TIMEOUT) + reset(event.getDownTime()); + break; + case MotionEvent.ACTION_POINTER_UP: + if(event.getPointerCount() == 2) + mTwoFingerTapCount++; + else + mFirstDownTime = 0; + break; + case MotionEvent.ACTION_UP: + if(!mSeparateTouches) + mSeparateTouches = true; + else if(mTwoFingerTapCount == 2 && event.getEventTime() - mFirstDownTime < TIMEOUT) { + parent.sendEvent("showMenu"); + mFirstDownTime = 0; + return true; + } + } + + return false; + } + +} http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f30b3bc6/AppHarnessUI/plugin.xml ---------------------------------------------------------------------- diff --git a/AppHarnessUI/plugin.xml b/AppHarnessUI/plugin.xml index e23d54d..5791d1b 100644 --- a/AppHarnessUI/plugin.xml +++ b/AppHarnessUI/plugin.xml @@ -31,7 +31,10 @@ </js-module> <platform name="android"> - <source-file src="AppHarnessUI.java" target-dir="src/org/apache/appharness" /> + <source-file src="android/AppHarnessUI.java" target-dir="src/org/apache/appharness" /> + <source-file src="android/CustomAndroidWebView.java" target-dir="src/org/apache/appharness" /> + <source-file src="android/CustomCordovaWebView.java" target-dir="src/org/apache/appharness" /> + <source-file src="android/TwoFingerDoubleTapGestureDetector.java" target-dir="src/org/apache/appharness" /> <config-file target="config.xml" parent="/*"> <feature name="AppHarnessUI"> http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f30b3bc6/template-overrides/Activity.java ---------------------------------------------------------------------- diff --git a/template-overrides/Activity.java b/template-overrides/Activity.java index 1a6de33..f602fe1 100644 --- a/template-overrides/Activity.java +++ b/template-overrides/Activity.java @@ -2,7 +2,7 @@ @Override public void onBackPressed() { // If app is running, quit it. - AppHarnessUI ahui = (AppHarnessUI)appView.getPlugin("AppHarnessUI"); + AppHarnessUI ahui = (AppHarnessUI)appView.getPluginManager().getPlugin("AppHarnessUI"); if (ahui != null) { if (ahui.isSlaveCreated()) { ahui.sendEvent("quitApp"); @@ -17,7 +17,7 @@ public Object onMessage(String id, Object data) { // Capture the app calling navigator.app.exitApp(). if ("exit".equals(id)) { - AppHarnessUI ahui = (AppHarnessUI)appView.getPlugin("AppHarnessUI"); + AppHarnessUI ahui = (AppHarnessUI)appView.getPluginManager().getPlugin("AppHarnessUI"); if (ahui != null) { if (ahui.isSlaveCreated()) { ahui.sendEvent("quitApp");
