You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ag...@apache.org on 2014/05/29 19:52:43 UTC

[1/3] git commit: Delete pinch to show overlay gesture.

Repository: cordova-app-harness
Updated Branches:
  refs/heads/master ba3cb0edf -> f21635e6d


Delete pinch to show overlay gesture.


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/daff4a57
Tree: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/tree/daff4a57
Diff: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/diff/daff4a57

Branch: refs/heads/master
Commit: daff4a57594253357822c21adfd46aa2d201b68b
Parents: ba3cb0e
Author: Andrew Grieve <ag...@chromium.org>
Authored: Thu May 29 10:54:36 2014 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Thu May 29 10:54:36 2014 -0400

----------------------------------------------------------------------
 AppHarnessUI/AppHarnessUI.java | 25 +------------------------
 AppHarnessUI/AppHarnessUI.m    | 14 +-------------
 2 files changed, 2 insertions(+), 37 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/daff4a57/AppHarnessUI/AppHarnessUI.java
----------------------------------------------------------------------
diff --git a/AppHarnessUI/AppHarnessUI.java b/AppHarnessUI/AppHarnessUI.java
index a8c2dd7..2a80dd8 100644
--- a/AppHarnessUI/AppHarnessUI.java
+++ b/AppHarnessUI/AppHarnessUI.java
@@ -36,8 +36,6 @@ import android.content.Context;
 import android.os.Build;
 import android.util.Log;
 import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.ScaleGestureDetector.OnScaleGestureListener;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
@@ -258,39 +256,18 @@ public class AppHarnessUI extends CordovaPlugin {
 
     }
 
-    private class CustomCordovaWebView extends CordovaWebView implements OnScaleGestureListener {
-        ScaleGestureDetector scaleGestureDetector;
+    private class CustomCordovaWebView extends CordovaWebView {
         TwoFingerDoubleTapGestureDetector twoFingerTapDetector;
 
         public CustomCordovaWebView(Context context) {
             super(context);
-            scaleGestureDetector = new ScaleGestureDetector(context, this);
             twoFingerTapDetector = new TwoFingerDoubleTapGestureDetector();
         }
 
         @Override
         public boolean onTouchEvent(MotionEvent e) {
-            scaleGestureDetector.onTouchEvent(e);
             twoFingerTapDetector.onTouchEvent(e);
             return super.onTouchEvent(e);
         }
-
-        @Override
-        public boolean onScale(ScaleGestureDetector detector) {
-            if (detector.getScaleFactor() < 0.6) {
-                sendEvent("showMenu");
-                return true;
-            }
-            return false;
-        }
-
-        @Override
-        public boolean onScaleBegin(ScaleGestureDetector detector) {
-            return true;
-        }
-
-        @Override
-        public void onScaleEnd(ScaleGestureDetector detector) {
-        }
     }
 }

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/daff4a57/AppHarnessUI/AppHarnessUI.m
----------------------------------------------------------------------
diff --git a/AppHarnessUI/AppHarnessUI.m b/AppHarnessUI/AppHarnessUI.m
index f14c60a..710c823 100644
--- a/AppHarnessUI/AppHarnessUI.m
+++ b/AppHarnessUI/AppHarnessUI.m
@@ -62,19 +62,16 @@
 
 - (void)viewDidLoad {
     [super viewDidLoad];
-    UIPinchGestureRecognizer *pinchRecognizer =
-        [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
     UITapGestureRecognizer *tapRecognizer =
         [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
 
     // Two-finger double-tap.
     tapRecognizer.numberOfTapsRequired = 2;
     tapRecognizer.numberOfTouchesRequired = 2;
+    tapRecognizer.delegate = self;
 
     // Add the tap gesture recognizer to the view
     [self.view addGestureRecognizer:tapRecognizer];
-    [self.view addGestureRecognizer:pinchRecognizer];
-    tapRecognizer.delegate = self;
 }
 
 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
@@ -82,15 +79,6 @@
     return YES;
 }
 
-- (void)handlePinch:(UIPinchGestureRecognizer *)recognizer {
-    if (recognizer.enabled && recognizer.scale < 0.3) {
-        // Stop callbacks for this gesture.
-        recognizer.enabled = NO;
-        recognizer.enabled = YES;
-        [_parentPlugin sendEvent:@"showMenu"];
-    }
-}
-
 - (void)handleTap:(UITapGestureRecognizer *)recognizer {
     [_parentPlugin sendEvent:@"showMenu"];
 }


[2/3] git commit: Don't use UrlRemap on iOS (buggy with multiple webviews)

Posted by ag...@apache.org.
Don't use UrlRemap on iOS (buggy with multiple webviews)


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/425a9e2b
Tree: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/tree/425a9e2b
Diff: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/diff/425a9e2b

Branch: refs/heads/master
Commit: 425a9e2bff0a81349ea58328919d74b81098fc40
Parents: daff4a5
Author: Andrew Grieve <ag...@chromium.org>
Authored: Thu May 29 13:49:19 2014 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Thu May 29 13:49:19 2014 -0400

----------------------------------------------------------------------
 www/cdvah/js/Installer.js       | 56 +++++++++++++++++++++---------------
 www/cdvah/js/ResourcesLoader.js | 12 ++++++++
 2 files changed, 45 insertions(+), 23 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/425a9e2b/www/cdvah/js/Installer.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/Installer.js b/www/cdvah/js/Installer.js
index 04c6d38..28acb24 100644
--- a/www/cdvah/js/Installer.js
+++ b/www/cdvah/js/Installer.js
@@ -124,35 +124,45 @@
             return $q.when(this._prepareForLaunch())
             .then(function() {
                 var urlutil = cordova.require('cordova/urlutil');
-                var harnessUrl = urlutil.makeAbsolute(location.pathname);
-                var harnessDir = harnessUrl.replace(/\/[^\/]*\/[^\/]*$/, '');
-                var realWwwUrl = self.directoryManager.rootURL + 'www';
+                var harnessWwwUrl = urlutil.makeAbsolute(location.pathname).replace(/\/[^\/]*\/[^\/]*$/, '/');
+                var appWwwUrl = self.directoryManager.rootURL + 'www/';
                 var startLocation = urlutil.makeAbsolute(self.startPage).replace('/cdvah/', '/');
-                var realStartLocation = startLocation.replace(harnessDir, realWwwUrl);
-
-                // Point right at the dest. location on iOS.
-                if (platformId == 'ios') {
-                    startLocation = realStartLocation;
-                }
+                var realStartLocation = startLocation.replace(harnessWwwUrl, appWwwUrl);
+                var useRemapper = platformId == 'android';
 
                 if (!/^file:/.exec(startLocation)) {
                     throw new Error('Expected to start with file: ' + startLocation);
                 }
 
-                // Override cordova.js, and www/plugins to point at bundled plugins.
-                UrlRemap.aliasUri('^(?!app-harness://).*/www/cordova\\.js.*', '.+', 'app-harness:///cordova.js', false /* redirect */, true /* allowFurtherRemapping */);
-                UrlRemap.aliasUri('^(?!app-harness://).*/www/plugins/.*', '^.*?/www/plugins/' , 'app-harness:///plugins/', false /* redirect */, true /* allowFurtherRemapping */);
-
-                // Make any references to www/ point to the app's install location.
-                var harnessPrefixPattern = '^' + harnessDir.replace('file:///', 'file://.*?/');
-                UrlRemap.aliasUri(harnessPrefixPattern, harnessPrefixPattern, realWwwUrl, false /* redirect */, true /* allowFurtherRemapping */);
-
-                // Set-up app-harness: scheme to point at the harness.
-                UrlRemap.aliasUri('^app-harness:///cdvah/index.html', '^app-harness://', harnessDir, true, false);
-                return UrlRemap.aliasUri('^app-harness:', '^app-harness://', harnessDir, false, false)
-                .then(function() {
-                    return startLocation;
-                });
+                if (useRemapper) {
+                    // Override cordova.js, and www/plugins to point at bundled plugins.
+                    UrlRemap.aliasUri('^(?!app-harness://).*/www/cordova\\.js.*', '.+', 'app-harness:///cordova.js', false /* redirect */, true /* allowFurtherRemapping */);
+                    UrlRemap.aliasUri('^(?!app-harness://).*/www/plugins/.*', '^.*?/www/plugins/' , 'app-harness:///plugins/', false /* redirect */, true /* allowFurtherRemapping */);
+
+                    // Make any references to www/ point to the app's install location.
+                    var harnessPrefixPattern = '^' + harnessWwwUrl.replace('file:///', 'file://.*?/');
+                    UrlRemap.aliasUri(harnessPrefixPattern, harnessPrefixPattern, appWwwUrl, false /* redirect */, true /* allowFurtherRemapping */);
+
+                    // Set-up app-harness: scheme to point at the harness.
+                    UrlRemap.aliasUri('^app-harness:///cdvah/index.html', '^app-harness://', harnessWwwUrl, true, false);
+                    return UrlRemap.aliasUri('^app-harness:', '^app-harness://', harnessWwwUrl, false, false)
+                    .then(function() {
+                        return startLocation;
+                    });
+                } else {
+                    return ResourcesLoader.delete(appWwwUrl + 'plugins/')
+                    .then(function() {
+                        return ResourcesLoader.delete(appWwwUrl + 'cordova.js');
+                    }).then(function() {
+                        return ResourcesLoader.delete(appWwwUrl + 'plugins/');
+                    }).then(function() {
+                        return ResourcesLoader.copy(harnessWwwUrl + 'cordova.js', appWwwUrl + 'cordova.js');
+                    }).then(function() {
+                        return ResourcesLoader.copy(harnessWwwUrl + 'plugins/', appWwwUrl + 'plugins/');
+                    }).then(function() {
+                        return realStartLocation;
+                    });
+                }
             });
         };
 

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/425a9e2b/www/cdvah/js/ResourcesLoader.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/ResourcesLoader.js b/www/cdvah/js/ResourcesLoader.js
index a0751df..2376ed9 100644
--- a/www/cdvah/js/ResourcesLoader.js
+++ b/www/cdvah/js/ResourcesLoader.js
@@ -169,6 +169,18 @@
                 return writeToFile(url, contents, true /* append */);
             },
 
+            copy: function(fromUrl, toUrl) {
+                return resolveURL(fromUrl)
+                .then(function(fromEntry) {
+                    return ensureDirectoryExists(dirName(toUrl))
+                    .then(function(destEntry) {
+                        var deferred = $q.defer();
+                        fromEntry.copyTo(destEntry, baseName(toUrl), deferred.resolve, deferred.reject);
+                        return deferred.promise;
+                    });
+                });
+            },
+
             moveFile: function(fromUrl, toUrl) {
                 return resolveURL(fromUrl)
                 .then(function(fromEntry) {


[3/3] git commit: Rework Overlay menu to be another view within the main webview.

Posted by ag...@apache.org.
Rework Overlay menu to be another view within the main webview.

Much more performant and less complicated this way!


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/f21635e6
Tree: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/tree/f21635e6
Diff: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/diff/f21635e6

Branch: refs/heads/master
Commit: f21635e6d09f893d69e775a463b796a788469e63
Parents: 425a9e2
Author: Andrew Grieve <ag...@chromium.org>
Authored: Thu May 29 13:50:44 2014 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Thu May 29 13:52:07 2014 -0400

----------------------------------------------------------------------
 AppHarnessUI/AppHarnessUI.java | 76 +++++++++++------------------------
 AppHarnessUI/appharnessui.js   | 10 ++---
 www/cdvah/css/style.css        |  5 +++
 www/cdvah/harnessmenu.html     |  1 +
 www/cdvah/js/AppHarnessUI.js   |  9 +----
 www/cdvah/js/AppsService.js    | 23 ++---------
 www/cdvah/js/InAppMenuCtrl.js  | 70 ++++++++++++++++++++++++++++++++
 www/cdvah/js/app.js            |  4 ++
 www/cdvah/views/inappmenu.html | 24 +++++++++++
 www/cdvahcm/contextMenu.html   | 80 -------------------------------------
 10 files changed, 136 insertions(+), 166 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f21635e6/AppHarnessUI/AppHarnessUI.java
----------------------------------------------------------------------
diff --git a/AppHarnessUI/AppHarnessUI.java b/AppHarnessUI/AppHarnessUI.java
index 2a80dd8..9e6eb82 100644
--- a/AppHarnessUI/AppHarnessUI.java
+++ b/AppHarnessUI/AppHarnessUI.java
@@ -27,7 +27,6 @@ import org.apache.cordova.CordovaWebView;
 import org.apache.cordova.CordovaWebViewClient;
 import org.apache.cordova.IceCreamCordovaWebViewClient;
 import org.apache.cordova.LinearLayoutSoftKeyboardDetect;
-import org.apache.cordova.PluginEntry;
 import org.apache.cordova.PluginResult;
 import org.json.JSONException;
 
@@ -46,13 +45,9 @@ public class AppHarnessUI extends CordovaPlugin {
     ViewGroup contentView;
     View origMainView;
     CustomCordovaWebView slaveWebView;
-    CordovaWebView overlayWebView;
+    boolean slaveVisible;
     CallbackContext eventsCallback;
 
-    public CordovaWebView getSlave() {
-        return slaveWebView;
-    }
-
     @Override
     public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException {
         if ("create".equals(action)) {
@@ -68,17 +63,11 @@ public class AppHarnessUI extends CordovaPlugin {
                     destroy(callbackContext);
                 }
             });
-        } else if ("createOverlay".equals(action)) {
-            final String url = args.getString(0);
-            this.cordova.getActivity().runOnUiThread(new Runnable() {
-                public void run() {
-                    createOverlay(url, callbackContext);
-                }
-            });
-        } else if ("destroyOverlay".equals(action)) {
+        } else if ("setVisible".equals(action)) {
+            final boolean value = args.getBoolean(0);
             this.cordova.getActivity().runOnUiThread(new Runnable() {
                 public void run() {
-                    destroyOverlay(callbackContext);
+                    setSlaveVisible(value, callbackContext);
                 }
             });
         } else if ("evalJs".equals(action)) {
@@ -96,7 +85,7 @@ public class AppHarnessUI extends CordovaPlugin {
         return true;
     }
 
-    void sendEvent(String eventName) {
+    private void sendEvent(String eventName) {
         PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, eventName);
         pluginResult.setKeepCallback(true);
         eventsCallback.sendPluginResult(pluginResult );
@@ -127,9 +116,8 @@ public class AppHarnessUI extends CordovaPlugin {
             if (activity.getBooleanProperty("DisallowOverscroll", false)) {
                 slaveWebView.setOverScrollMode(CordovaWebView.OVER_SCROLL_NEVER);
             }
-            contentView.removeAllViews();
-            contentView.addView((View)slaveWebView.getParent());
             slaveWebView.loadUrl(url);
+            setSlaveVisible(true, null);
         }
         callbackContext.success();
     }
@@ -138,45 +126,38 @@ public class AppHarnessUI extends CordovaPlugin {
         if (slaveWebView == null) {
             Log.w(LOG_TAG, "destroy: already destroyed");
         } else {
-            contentView.removeAllViews();
-            contentView.addView(origMainView);
+            setSlaveVisible(false, null);
             slaveWebView.destroy();
             slaveWebView = null;
+            sendEvent("destroyed");
         }
         if (eventsCallback != null) {
-            eventsCallback.success();
+            eventsCallback.success("");
             eventsCallback = null;
         }
         callbackContext.success();
     }
 
-    private void createOverlay(String url, CallbackContext callbackContext) {
-        if (overlayWebView != null) {
-            Log.w(LOG_TAG, "createOverlay: already exists");
+    private void setSlaveVisible(boolean value, CallbackContext callbackContext) {
+        if (value == slaveVisible) {
+            return;
+        }
+        if (slaveWebView == null) {
+            Log.w(LOG_TAG, "setSlaveVisible: slave not created");
         } else {
-            overlayWebView = new CordovaWebView(cordova.getActivity());
-            initWebView(overlayWebView);
-            overlayWebView.pluginManager.addService(new PluginEntry("OverlayPlugin", new OverlayPlugin()));
-            overlayWebView.setOverScrollMode(CordovaWebView.OVER_SCROLL_NEVER);
-            contentView.addView((View)overlayWebView.getParent());
-            overlayWebView.loadUrl(url);
+            slaveVisible = value;
+            contentView.removeAllViews();
+            View newView = value ? (View)slaveWebView.getParent() : origMainView;
+            // Back button capturing breaks without these:
+            contentView.addView(newView);
+            newView.requestFocus();
 
         }
-        callbackContext.success();
-    }
-
-    private void destroyOverlay(CallbackContext callbackContext) {
-        if (overlayWebView == null) {
-            Log.w(LOG_TAG, "destroyOverlay: already destroyed");
-        } else {
-            contentView.removeView((View)overlayWebView.getParent());
-            overlayWebView.destroy();
-            overlayWebView = null;
+        if (callbackContext != null) {
+            callbackContext.success();
         }
-        callbackContext.success();
     }
 
-
     private void initWebView(CordovaWebView newWebView) {
         CordovaActivity activity = (CordovaActivity)cordova.getActivity();
         if (contentView == null) {
@@ -205,17 +186,6 @@ public class AppHarnessUI extends CordovaPlugin {
         layoutView.addView(newWebView);
     }
 
-    private class OverlayPlugin extends CordovaPlugin {
-        @Override
-        public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException {
-            if ("sendEvent".equals(action)) {
-                sendEvent(args.getString(0));
-                return true;
-            }
-            return false;
-        }
-    }
-
     // 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;

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f21635e6/AppHarnessUI/appharnessui.js
----------------------------------------------------------------------
diff --git a/AppHarnessUI/appharnessui.js b/AppHarnessUI/appharnessui.js
index 65864a6..a902f60 100644
--- a/AppHarnessUI/appharnessui.js
+++ b/AppHarnessUI/appharnessui.js
@@ -20,7 +20,7 @@
 var exec = cordova.require('cordova/exec');
 
 function eventHandler(type) {
-    exports.onEvent && exports.onEvent(type);
+    type && exports.onEvent && exports.onEvent(type);
 }
 
 exports.onEvent = null;
@@ -34,12 +34,8 @@ exports.destroy = function(win) {
     exec(win, null, 'AppHarnessUI', 'destroy', []);
 };
 
-exports.createOverlay = function(url, win) {
-    exec(win, null, 'AppHarnessUI', 'createOverlay', [url]);
-};
-
-exports.destroyOverlay = function(win) {
-    exec(win, null, 'AppHarnessUI', 'destroyOverlay', []);
+exports.setVisible = function(value, win) {
+    exec(win, null, 'AppHarnessUI', 'setVisible', [value]);
 };
 
 exports.evalJs = function(code, win) {

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f21635e6/www/cdvah/css/style.css
----------------------------------------------------------------------
diff --git a/www/cdvah/css/style.css b/www/cdvah/css/style.css
index daab765..a52ee24 100644
--- a/www/cdvah/css/style.css
+++ b/www/cdvah/css/style.css
@@ -55,3 +55,8 @@
 .notification-error {
     background-color: #f2dede;
 }
+
+.in-app-menu-container button {
+    margin: 20px;
+    text-align: center;
+}

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f21635e6/www/cdvah/harnessmenu.html
----------------------------------------------------------------------
diff --git a/www/cdvah/harnessmenu.html b/www/cdvah/harnessmenu.html
index 41b9d6a..8526930 100644
--- a/www/cdvah/harnessmenu.html
+++ b/www/cdvah/harnessmenu.html
@@ -35,6 +35,7 @@
         <script type="text/javascript" src="js/UrlRemap.js"></script>
         <script type="text/javascript" src="js/ListCtrl.js"></script>
         <script type="text/javascript" src="js/DetailsCtrl.js"></script>
+        <script type="text/javascript" src="js/InAppMenuCtrl.js"></script>
         <script type="text/javascript" src="js/Notify.js"></script>
         <script type="text/javascript" src="js/HttpServer.js"></script>
         <script type="text/javascript" src="js/HarnessServer.js"></script>

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f21635e6/www/cdvah/js/AppHarnessUI.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/AppHarnessUI.js b/www/cdvah/js/AppHarnessUI.js
index eaaca59..01abf65 100644
--- a/www/cdvah/js/AppHarnessUI.js
+++ b/www/cdvah/js/AppHarnessUI.js
@@ -31,14 +31,9 @@
                 cordova.plugins.appharnessui.destroy(deferred.resolve);
                 return deferred.promise;
             },
-            createOverlay: function() {
+            setVisible: function(value) {
                 var deferred = $q.defer();
-                cordova.plugins.appharnessui.createOverlay('app-harness:///cdvahcm/contextMenu.html', deferred.resolve);
-                return deferred.promise;
-            },
-            destroyOverlay: function() {
-                var deferred = $q.defer();
-                cordova.plugins.appharnessui.destroyOverlay(deferred.resolve);
+                cordova.plugins.appharnessui.setVisible(value, deferred.resolve);
                 return deferred.promise;
             },
             setEventHandler: function(f) {

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f21635e6/www/cdvah/js/AppsService.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/AppsService.js b/www/cdvah/js/AppsService.js
index 43adc0c..a7cb71a 100644
--- a/www/cdvah/js/AppsService.js
+++ b/www/cdvah/js/AppsService.js
@@ -19,7 +19,7 @@
 (function() {
     'use strict';
     /* global myApp */
-    myApp.factory('AppsService', ['$q', 'ResourcesLoader', 'INSTALL_DIRECTORY', 'APPS_JSON', 'notifier', 'AppHarnessUI', function($q, ResourcesLoader, INSTALL_DIRECTORY, APPS_JSON, notifier, AppHarnessUI) {
+    myApp.factory('AppsService', ['$q', '$location', 'ResourcesLoader', 'INSTALL_DIRECTORY', 'APPS_JSON', 'notifier', 'AppHarnessUI', function($q, $location, ResourcesLoader, INSTALL_DIRECTORY, APPS_JSON, notifier, AppHarnessUI) {
         // Map of type -> installer.
         var _installerFactories = Object.create(null);
         // Array of installer objects.
@@ -90,23 +90,6 @@
             return ResourcesLoader.writeFileContents(APPS_JSON, stringContents);
         }
 
-        AppHarnessUI.setEventHandler(function(eventName) {
-            console.log('Got event from UI: ' + eventName);
-            if (eventName == 'showMenu') {
-                AppHarnessUI.createOverlay();
-            } else if (eventName == 'hideMenu') {
-                AppHarnessUI.destroyOverlay();
-            } else if (eventName == 'restartApp') {
-                // TODO: Restart in place?
-                AppsService.launchApp(activeInstaller)
-                .then(null, notifier.error);
-            } else if (eventName == 'quitApp') {
-                AppsService.quitApp();
-            } else {
-                console.warn('Unknown message from AppHarnessUI: ' + eventName);
-            }
-        });
-
         var AppsService = {
             // return promise with the array of apps
             getAppList : function() {
@@ -149,8 +132,8 @@
             quitApp : function() {
                 if (activeInstaller) {
                     activeInstaller.unlaunch();
-                    AppHarnessUI.destroy();
                     activeInstaller = null;
+                    return AppHarnessUI.destroy();
                 }
                 return $q.when();
             },
@@ -167,6 +150,8 @@
                         return AppHarnessUI.create(launchUrl);
                     }, function() {
                         throw new Error('Start file does not exist: ' + launchUrl.replace(/.*?\/www\//, 'www/'));
+                    }).then(function() {
+                        $location.path('/inappmenu');
                     });
                 });
             },

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f21635e6/www/cdvah/js/InAppMenuCtrl.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/InAppMenuCtrl.js b/www/cdvah/js/InAppMenuCtrl.js
new file mode 100644
index 0000000..1ffb1da
--- /dev/null
+++ b/www/cdvah/js/InAppMenuCtrl.js
@@ -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.
+*/
+(function(){
+    'use strict';
+
+    /* global myApp */
+    myApp.controller('InAppMenuCtrl', ['$rootScope', '$scope', '$window', 'AppsService', 'AppHarnessUI', function($rootScope, $scope, $window, AppsService, AppHarnessUI) {
+        var activeApp = AppsService.getActiveApp();
+        if (!activeApp) {
+            $window.history.back();
+        }
+
+        function backButtonListener() {
+            $scope.$apply($scope.hideMenu);
+        }
+        document.addEventListener('backbutton', backButtonListener);
+        $scope.$on('$destroy', function() {
+            document.removeEventListener('backbutton', backButtonListener);
+        });
+
+        $scope.app = activeApp;
+
+        $scope.hideMenu = function() {
+            return AppHarnessUI.setVisible(true);
+        };
+
+        $scope.restartApp = function() {
+            return AppsService.launchApp(activeApp);
+        };
+
+        $scope.quitApp = function() {
+            return AppsService.quitApp();
+        };
+
+        AppHarnessUI.setEventHandler(function(eventName) {
+            $scope.$apply(function() {
+                if (eventName == 'showMenu') {
+                    AppHarnessUI.setVisible(false);
+                } else if (eventName == 'hideMenu') {
+                    AppHarnessUI.setVisible(true);
+                } else if (eventName == 'restartApp') {
+                    // TODO: Restart in place?
+                    AppsService.launchApp(activeApp);
+                } else if (eventName == 'quitApp') {
+                    AppsService.quitApp();
+                } else if (eventName == 'destroyed') {
+                    $window.history.back();
+                } else {
+                    console.warn('Unknown message from AppHarnessUI: ' + eventName);
+                }
+            });
+        });
+    }]);
+})();

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f21635e6/www/cdvah/js/app.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/app.js b/www/cdvah/js/app.js
index 8b84a1a..df30b53 100644
--- a/www/cdvah/js/app.js
+++ b/www/cdvah/js/app.js
@@ -27,6 +27,10 @@ myApp.config(['$routeProvider', function($routeProvider){
         templateUrl: 'views/list.html',
         controller: 'ListCtrl'
     });
+    $routeProvider.when('/inappmenu', {
+        templateUrl: 'views/inappmenu.html',
+        controller: 'InAppMenuCtrl'
+    });
     $routeProvider.when('/details/:index', {
         templateUrl: 'views/details.html',
         controller: 'DetailsCtrl'

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f21635e6/www/cdvah/views/inappmenu.html
----------------------------------------------------------------------
diff --git a/www/cdvah/views/inappmenu.html b/www/cdvah/views/inappmenu.html
new file mode 100644
index 0000000..404735c
--- /dev/null
+++ b/www/cdvah/views/inappmenu.html
@@ -0,0 +1,24 @@
+<!--
+  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.
+-->
+<div class="in-app-menu-container">
+  <h1>{{app.appName || app.appId}}</h1>
+  <button ng-click="hideMenu()">Resume App</button>
+  <button ng-click="restartApp()">Restart App</button><br>
+  <button ng-click="quitApp()">Quit App</button>
+</div>

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/f21635e6/www/cdvahcm/contextMenu.html
----------------------------------------------------------------------
diff --git a/www/cdvahcm/contextMenu.html b/www/cdvahcm/contextMenu.html
deleted file mode 100644
index 39848aa..0000000
--- a/www/cdvahcm/contextMenu.html
+++ /dev/null
@@ -1,80 +0,0 @@
-<!DOCTYPE html>
-<!--
-  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.
--->
-<html onclick="sendEvent('hideMenu')">
-<head>
-    <style>
-        body
-        {
-            background-color:rgba(0,0,0,0.75);
-            width: 100%;
-            height: 100%;
-        }
-        p, label
-        {
-            color: white;
-            text-align: center;
-        }
-        ul
-        {
-            list-style-type: none;
-            margin-left: 20%;
-            margin-right: 20%;
-            padding: 0;
-            width: 60%;
-        }
-        li
-        {
-            margin-bottom: 0.5cm;
-            width : 100%;
-            min-height: 1cm;
-            border-bottom: 1px solid grey;
-        }
-        .fullwidthElement
-        {
-            width: 100%;
-            min-height: 1cm;
-        }
-        .halfwidthElement
-        {
-            min-height: 1cm;
-            width: 48%;
-            padding-left: 0;
-            padding-right: 0;
-        }
-    </style>
-    <script src="app-harness:///cordova.js"></script>
-</head>
-<body>
-    <p>Tap Anywhere to Close</p>
-    <ul>
-        <li>
-            <button class="fullwidthElement" onclick="sendEvent('restartApp')">Restart</button>
-        </li>
-        <li>
-            <button class="fullwidthElement" onclick="sendEvent('quitApp')">Back to Main Menu</button>
-        </li>
-    </ul>
-</body>
-<script>
-  function sendEvent(eventName) {
-      cordova.exec(null, null, 'OverlayPlugin', 'sendEvent', [eventName]);
-  }
-</script>
-</html>