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");

Reply via email to