Fix IntentHandler behavior

 Bug: 4473779
 Plus, TESTS! Huzzah!

Change-Id: I043e100d99d4653b7fb7885217d7fb305930a137
diff --git a/src/com/android/browser/BookmarkUtils.java b/src/com/android/browser/BookmarkUtils.java
index 491c16c..23765f4 100644
--- a/src/com/android/browser/BookmarkUtils.java
+++ b/src/com/android/browser/BookmarkUtils.java
@@ -115,10 +115,7 @@
     static Intent createAddToHomeIntent(Context context, String url, String title,
             Bitmap touchIcon, Bitmap favicon) {
         Intent i = new Intent(INSTALL_SHORTCUT);
-        Intent shortcutIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
-        long urlHash = url.hashCode();
-        long uniqueId = (urlHash << 32) | shortcutIntent.hashCode();
-        shortcutIntent.putExtra(Browser.EXTRA_APPLICATION_ID, Long.toString(uniqueId));
+        Intent shortcutIntent = createShortcutIntent(url);
         i.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
         i.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
         i.putExtra(Intent.EXTRA_SHORTCUT_ICON, createIcon(context, touchIcon, favicon,
@@ -129,6 +126,14 @@
         return i;
     }
 
+    static Intent createShortcutIntent(String url) {
+        Intent shortcutIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+        long urlHash = url.hashCode();
+        long uniqueId = (urlHash << 32) | shortcutIntent.hashCode();
+        shortcutIntent.putExtra(Browser.EXTRA_APPLICATION_ID, Long.toString(uniqueId));
+        return shortcutIntent;
+    }
+
     private static Bitmap getIconBackground(Context context, BookmarkIconType type, int density) {
         if (type == BookmarkIconType.ICON_HOME_SHORTCUT) {
             // Want to create a shortcut icon on the homescreen, so the icon
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index 9a015b2..1f376d2 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -338,6 +338,8 @@
             // TabControl.restoreState() will create a new tab even if
             // restoring the state fails.
             setActiveTab(mTabControl.getCurrentTab());
+            // Handle the intent
+            mIntentHandler.onNewIntent(intent);
         }
         // clear up the thumbnail directory, which is no longer used;
         // ideally this should only be run once after an upgrade from
diff --git a/src/com/android/browser/IntentHandler.java b/src/com/android/browser/IntentHandler.java
index 54711d9..088a788 100644
--- a/src/com/android/browser/IntentHandler.java
+++ b/src/com/android/browser/IntentHandler.java
@@ -139,6 +139,15 @@
                 mController.openTab(urlData);
                 return;
             }
+            /*
+             * TODO: Don't allow javascript URIs
+             * 0) If this is a javascript: URI, *always* open a new tab
+             * 1) If this is a voice search, re-use tab for appId
+             *    If there is no appId, use current tab
+             * 2) If the URL is already opened, switch to that tab
+             * 3-phone) Reuse tab with same appId
+             * 3-tablet) Open new tab
+             */
             final String appId = intent
                     .getStringExtra(Browser.EXTRA_APPLICATION_ID);
             if (!TextUtils.isEmpty(urlData.mUrl) &&
@@ -151,38 +160,33 @@
                     // If a voice search has no appId, it means that it came
                     // from the browser.  In that case, reuse the current tab.
                     || (activateVoiceSearch && appId != null))
-                    && !mActivity.getPackageName().equals(appId)
-                    && (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
-                if (activateVoiceSearch) {
+                    && !mActivity.getPackageName().equals(appId)) {
+                if (activateVoiceSearch || !BrowserActivity.isTablet(mActivity)) {
                     Tab appTab = mTabControl.getTabFromAppId(appId);
                     if (appTab != null) {
                         mController.reuseTab(appTab, urlData);
                         return;
-                    } else {
-                        Tab tab = mController.openTab(urlData);
-                        if (tab != null) {
-                            tab.setAppId(appId);
-                        }
                     }
+                }
+                // No matching application tab, try to find a regular tab
+                // with a matching url.
+                Tab appTab = mTabControl.findTabWithUrl(urlData.mUrl);
+                if (appTab != null) {
+                    // Transfer ownership
+                    appTab.setAppId(appId);
+                    if (current != appTab) {
+                        mController.switchToTab(appTab);
+                    }
+                    // Otherwise, we are already viewing the correct tab.
                 } else {
-                    // No matching application tab, try to find a regular tab
-                    // with a matching url.
-                    Tab appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl);
-                    if (appTab != null) {
-                        if (current != appTab) {
-                            mController.switchToTab(appTab);
-                        }
-                        // Otherwise, we are already viewing the correct tab.
-                    } else {
-                        // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url
-                        // will be opened in a new tab unless we have reached
-                        // MAX_TABS. Then the url will be opened in the current
-                        // tab. If a new tab is created, it will have "true" for
-                        // exit on close.
-                        Tab tab = mController.openTab(urlData);
-                        if (tab != null) {
-                            tab.setAppId(appId);
-                        }
+                    // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url
+                    // will be opened in a new tab unless we have reached
+                    // MAX_TABS. Then the url will be opened in the current
+                    // tab. If a new tab is created, it will have "true" for
+                    // exit on close.
+                    Tab tab = mController.openTab(urlData);
+                    if (tab != null) {
+                        tab.setAppId(appId);
                     }
                 }
             } else {
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index c78b562..e672e2b 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -152,6 +152,7 @@
     // All the state needed for a page
     protected static class PageState {
         String mUrl;
+        String mOriginalUrl;
         String mTitle;
         LockIcon mLockIcon;
         Bitmap mFavicon;
@@ -159,10 +160,10 @@
 
         PageState(Context c, boolean incognito) {
             if (incognito) {
-                mUrl = "browser:incognito";
+                mOriginalUrl = mUrl = "browser:incognito";
                 mTitle = c.getString(R.string.new_incognito_tab);
             } else {
-                mUrl = "";
+                mOriginalUrl = mUrl = "";
                 mTitle = c.getString(R.string.new_tab);
             }
             mFavicon = BitmapFactory.decodeResource(
@@ -171,7 +172,7 @@
         }
 
         PageState(Context c, boolean incognito, String url, Bitmap favicon) {
-            mUrl = url;
+            mOriginalUrl = mUrl = url;
             mTitle = null;
             if (URLUtil.isHttpsUrl(url)) {
                 mLockIcon = LockIcon.LOCK_ICON_SECURE;
@@ -562,6 +563,7 @@
             if (mCurrentState.mUrl == null) {
                 mCurrentState.mUrl = url != null ? url : "";
             }
+            mCurrentState.mOriginalUrl = view.getOriginalUrl();
             mCurrentState.mTitle = view.getTitle();
             mCurrentState.mFavicon = view.getFavicon();
             if (!URLUtil.isHttpsUrl(mCurrentState.mUrl)) {
@@ -1677,10 +1679,10 @@
     }
 
     String getOriginalUrl() {
-        if (mMainView == null) {
-            return "";
+        if (mCurrentState.mOriginalUrl == null) {
+            return getUrl();
         }
-        return UrlUtils.filteredUrl(mMainView.getOriginalUrl());
+        return UrlUtils.filteredUrl(mCurrentState.mOriginalUrl);
     }
 
     /**
diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java
index 1367ba2..2eb24e9 100644
--- a/src/com/android/browser/TabControl.java
+++ b/src/com/android/browser/TabControl.java
@@ -551,40 +551,28 @@
         }
     }
 
-    // This method checks if a non-app tab (one created within the browser)
-    // matches the given url.
+    // This method checks if a tab matches the given url.
     private boolean tabMatchesUrl(Tab t, String url) {
-        if (t.getAppId() != null) {
-            return false;
-        }
-        WebView webview = t.getWebView();
-        if (webview == null) {
-            return false;
-        } else if (url.equals(webview.getUrl())
-                || url.equals(webview.getOriginalUrl())) {
-            return true;
-        }
-        return false;
+        return url.equals(t.getUrl()) || url.equals(t.getOriginalUrl());
     }
 
     /**
-     * Return the tab that has no app id associated with it and the url of the
-     * tab matches the given url.
+     * Return the tab that matches the given url.
      * @param url The url to search for.
      */
-    Tab findUnusedTabWithUrl(String url) {
+    Tab findTabWithUrl(String url) {
         if (url == null) {
             return null;
         }
         // Check the current tab first.
-        Tab t = getCurrentTab();
-        if (t != null && tabMatchesUrl(t, url)) {
-            return t;
+        Tab currentTab = getCurrentTab();
+        if (currentTab != null && tabMatchesUrl(currentTab, url)) {
+            return currentTab;
         }
         // Now check all the rest.
         for (Tab tab : mTabs) {
             if (tabMatchesUrl(tab, url)) {
-                return t;
+                return tab;
             }
         }
         return null;
diff --git a/tests/src/com/android/browser/IntentHandlerTests.java b/tests/src/com/android/browser/IntentHandlerTests.java
new file mode 100644
index 0000000..2bdadae
--- /dev/null
+++ b/tests/src/com/android/browser/IntentHandlerTests.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed 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 com.android.browser;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.Browser;
+import android.test.ActivityInstrumentationTestCase2;
+import android.text.TextUtils;
+import android.webkit.WebView;
+
+public class IntentHandlerTests extends ActivityInstrumentationTestCase2<BrowserActivity> {
+
+    // How long to wait to receive onPageStarted
+    static final int START_LOAD_TIMEOUT = 20000; // ms
+    static final int POLL_INTERVAL = 50; // ms
+    boolean mHasStarted = false;
+
+    public IntentHandlerTests() {
+        super(BrowserActivity.class);
+    }
+
+    public void testSwitchToTabWithUrl() throws Throwable {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setData(Uri.parse("http://google.com/"));
+        sendIntent(intent);
+        Controller controller = getActivity().getController();
+        Tab tabGoogle = controller.getCurrentTab();
+        assertNotNull("Current tab (google.com", tabGoogle);
+        assertEquals("http://google.com/", tabGoogle.getOriginalUrl());
+        assertEquals(1, controller.getTabs().size());
+        intent.setData(Uri.parse("http://maps.google.com/"));
+        sendIntent(intent);
+        Tab tabMaps = controller.getCurrentTab();
+        assertNotSame(tabGoogle, tabMaps);
+        assertNotNull("Current tab (maps.google.com)", tabMaps);
+        assertEquals(2, controller.getTabs().size());
+        intent.setData(Uri.parse("http://google.com/"));
+        sendIntent(intent);
+        assertEquals(tabGoogle, controller.getCurrentTab());
+        assertEquals(2, controller.getTabs().size());
+    }
+
+    public void testShortcut() throws Throwable {
+        Intent intent = BookmarkUtils.createShortcutIntent("http://google.com/");
+        sendIntent(intent);
+        Controller controller = getActivity().getController();
+        Tab tabGoogle = controller.getCurrentTab();
+        assertEquals("http://google.com/", tabGoogle.getOriginalUrl());
+        assertEquals(1, controller.getTabs().size());
+        sendIntent(intent);
+        assertEquals(1, controller.getTabs().size());
+        assertEquals(tabGoogle, controller.getCurrentTab());
+        directlyLoadUrl(tabGoogle, "http://maps.google.com/");
+        sendIntent(intent);
+        if (BrowserActivity.isTablet(getActivity())) {
+            assertEquals(2, controller.getTabs().size());
+            assertNotSame(tabGoogle, controller.getCurrentTab());
+            assertEquals("http://maps.google.com/", tabGoogle.getOriginalUrl());
+            Tab currentTab = controller.getCurrentTab();
+            assertEquals("http://google.com/", currentTab.getOriginalUrl());
+        } else {
+            assertEquals(1, controller.getTabs().size());
+            assertEquals(tabGoogle, controller.getCurrentTab());
+            assertEquals("http://google.com/", tabGoogle.getOriginalUrl());
+        }
+    }
+
+    public void testApplication() throws Throwable {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setData(Uri.parse("http://google.com/"));
+        intent.putExtra(Browser.EXTRA_APPLICATION_ID, getClass().getName());
+        sendIntent(intent);
+        Controller controller = getActivity().getController();
+        Tab tabGoogle = controller.getCurrentTab();
+        assertNotNull("Current tab (google.com", tabGoogle);
+        assertEquals("http://google.com/", tabGoogle.getOriginalUrl());
+        assertEquals(1, controller.getTabs().size());
+        intent.setData(Uri.parse("http://maps.google.com/"));
+        sendIntent(intent);
+        Tab tabMaps = controller.getCurrentTab();
+        assertEquals("http://maps.google.com/", tabMaps.getOriginalUrl());
+        if (BrowserActivity.isTablet(getActivity())) {
+            assertEquals(2, controller.getTabs().size());
+            assertNotSame(tabGoogle, tabMaps);
+            assertEquals("http://google.com/", tabGoogle.getOriginalUrl());
+        } else {
+            assertEquals(1, controller.getTabs().size());
+            assertEquals(tabGoogle, tabMaps);
+        }
+    }
+
+    /**
+     * Simulate clicking a link by loading a URL directly on the WebView,
+     * bypassing Tab, Controller, etc..
+     * @throws Throwable
+     */
+    private void directlyLoadUrl(final Tab tab, final String url) throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                WebView web = tab.getWebView();
+                web.loadUrl(url);
+            }
+        });
+        waitForLoadStart(tab, url);
+    }
+
+    void waitForLoadStart(final Tab tab, final String url) throws InterruptedException {
+        long start = System.currentTimeMillis();
+        while (!TextUtils.equals(tab.getOriginalUrl(), url)) {
+            if (start + START_LOAD_TIMEOUT < System.currentTimeMillis()) {
+                throw new RuntimeException("Didn't receive onPageStarted!");
+            }
+            Thread.sleep(POLL_INTERVAL);
+        }
+    }
+
+    private void sendIntent(final Intent intent) throws Throwable {
+        sendIntent(intent, true);
+    }
+
+    private void sendIntent(final Intent intent, boolean waitForLoadStart) throws Throwable {
+        if (!mHasStarted) {
+            // Prevent crash recovery from happening
+            intent.putExtra(Controller.NO_CRASH_RECOVERY, true);
+            setActivityIntent(intent);
+            getActivity();
+        } else {
+            final Activity activity = getActivity();
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    getInstrumentation().callActivityOnNewIntent(activity, intent);
+                }
+            });
+        }
+        if (waitForLoadStart) {
+            String url = intent.getDataString();
+            Tab tab = getActivity().getController().getCurrentTab();
+            waitForLoadStart(tab, url);
+        }
+    }
+
+    @Override
+    public BrowserActivity getActivity() {
+        mHasStarted = true;
+        return super.getActivity();
+    }
+}