From c52262a4057f8a86a1082e4cfbab4e70ff195daf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Mart=C3=AD?= <mvdan@mvdan.cc>
Date: Wed, 8 Apr 2015 21:06:55 +0200
Subject: [PATCH] Handle all app and search links via the main activity

Closes #208.
---
 F-Droid/AndroidManifest.xml                   | 195 +++++++++---------
 F-Droid/src/org/fdroid/fdroid/AppDetails.java |  52 +----
 F-Droid/src/org/fdroid/fdroid/FDroid.java     | 123 ++++++++---
 .../src/org/fdroid/fdroid/SearchResults.java  |   2 -
 .../views/fragments/AppListFragment.java      |   5 +-
 .../fragments/SearchResultsFragment.java      |  18 +-
 6 files changed, 207 insertions(+), 188 deletions(-)

diff --git a/F-Droid/AndroidManifest.xml b/F-Droid/AndroidManifest.xml
index a2952656e..5a8a353ac 100644
--- a/F-Droid/AndroidManifest.xml
+++ b/F-Droid/AndroidManifest.xml
@@ -92,12 +92,35 @@
             android:launchMode="singleTop"
             android:configChanges="keyboardHidden|orientation|screenSize" >
 
+            <!-- App URLs -->
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
 
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
 
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+
+                <data android:scheme="fdroid.app" />
+            </intent-filter>
+
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="f-droid.org" />
+                <data android:host="www.f-droid.org" />
+                <data android:pathPrefix="/app/" />
+            </intent-filter>
+
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
 
@@ -111,6 +134,83 @@
                 <data android:pathPrefix="/repository/browse" />
             </intent-filter>
 
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+
+                <data android:scheme="market" android:host="details" />
+            </intent-filter>
+
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="play.google.com" /> <!-- they don't do www. -->
+                <data android:path="/store/apps/details" />
+            </intent-filter>
+
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+
+                <data android:scheme="amzn" android:host="apps" android:path="/android" />
+            </intent-filter>
+
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="amazon.com" />
+                <data android:host="www.amazon.com" />
+                <data android:path="/gp/mas/dl/android" />
+            </intent-filter>
+
+            <!-- Search URLs -->
+
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+
+                <data android:scheme="fdroid.search" />
+            </intent-filter>
+
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+
+                <data android:scheme="market" android:host="search" />
+            </intent-filter>
+
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:host="play.google.com" /> <!-- they don't do www. -->
+                <data android:path="/store/search" />
+            </intent-filter>
+
+            <!-- Repo URLs -->
+
             <!--
             This intent serves two purposes: Swapping apps between devices and adding a
             repo from a website (e.g. https://guardianproject.info/fdroid/repo).
@@ -263,71 +363,6 @@
                 android:name="android.support.PARENT_ACTIVITY"
                 android:value=".FDroid" />
 
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-
-                <data android:scheme="fdroid.app" />
-            </intent-filter>
-
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-
-                <data android:scheme="http" />
-                <data android:scheme="https" />
-                <data android:host="f-droid.org" />
-                <data android:host="www.f-droid.org" />
-                <data android:pathPrefix="/app/" />
-            </intent-filter>
-
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-
-                <data android:scheme="market" android:host="details" />
-            </intent-filter>
-
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-
-                <data android:scheme="http" />
-                <data android:scheme="https" />
-                <data android:host="play.google.com" /> <!-- they don't do www. -->
-                <data android:path="/store/apps/details" />
-            </intent-filter>
-
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-
-                <data android:scheme="amzn" android:host="apps" android:path="/android" />
-            </intent-filter>
-
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-
-                <data android:scheme="http" />
-                <data android:scheme="https" />
-                <data android:host="amazon.com" />
-                <data android:host="www.amazon.com" />
-                <data android:path="/gp/mas/dl/android" />
-            </intent-filter>
-
         </activity>
         <activity
             android:name=".views.swap.SwapAppListActivity$SwapAppDetails"
@@ -377,36 +412,6 @@
                 <action android:name="android.intent.action.SEARCH" />
             </intent-filter>
 
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-
-                <data android:scheme="fdroid.search" />
-            </intent-filter>
-
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-
-                <data android:scheme="market" android:host="search" />
-            </intent-filter>
-
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-
-                <data android:scheme="http" />
-                <data android:scheme="https" />
-                <data android:host="play.google.com" /> <!-- they don't do www. -->
-                <data android:path="/store/search" />
-            </intent-filter>
-
             <meta-data
                 android:name="android.app.searchable"
                 android:resource="@xml/searchable" />
diff --git a/F-Droid/src/org/fdroid/fdroid/AppDetails.java b/F-Droid/src/org/fdroid/fdroid/AppDetails.java
index b50144160..eb426ab3c 100644
--- a/F-Droid/src/org/fdroid/fdroid/AppDetails.java
+++ b/F-Droid/src/org/fdroid/fdroid/AppDetails.java
@@ -344,56 +344,18 @@ public class AppDetails extends ActionBarActivity implements ProgressListener, A
 
     /**
      * Attempt to extract the appId from the intent which launched this activity.
-     * Various different intents could cause us to show this activity, such as:
-     * <ul>
-     *     <li>market://details?id=[app_id]</li>
-     *     <li>https://play.google.com/store/apps/details?id=[app_id]</li>
-     *     <li>https://f-droid.org/app/[app_id]</li>
-     *     <li>fdroid.app:[app_id]</li>
-     * </ul>
-     * @return May return null, if we couldn't find the appId. In this case, you will
-     * probably want to do something drastic like finish the activity and show some
-     * feedback to the user (this method will <em>not</em> do that, it will just return
-     * null).
+     * @return May return null, if we couldn't find the appId. This should
+     * never happen as AppDetails is only to be called by the FDroid activity
+     * and not externally.
      */
     private String getAppIdFromIntent() {
         Intent i = getIntent();
         Uri data = i.getData();
-        String appId = null;
-        if (data != null) {
-            if (data.isHierarchical()) {
-                final String host = data.getHost();
-                if (host == null) {
-                    Log.e(TAG, "Null host found in app link!");
-                    return null;
-                }
-                if (host.equals("details") || host.equals("play.google.com")) {
-                    // market://details?id=app.id
-                    // https://play.google.com/store/apps/details?id=app.id
-                    appId = data.getQueryParameter("id");
-                } else if (host.equals("apps") || host.equals("amazon.com") ||
-                        host.equals("www.amazon.com")) {
-                    // amzn://apps/android?p=app.id
-                    // http://www.amazon.com/gp/mas/dl/android?p=app.id
-                    appId = data.getQueryParameter("p");
-                } else {
-                    // https://f-droid.org/app/app.id
-                    appId = data.getLastPathSegment();
-                    if (appId != null && appId.equals("app")) {
-                        appId = null;
-                    }
-                }
-            } else {
-                // fdroid.app:app.id
-                appId = data.getEncodedSchemeSpecificPart();
-            }
-            Log.d(TAG, "AppDetails launched from link, for '" + appId + "'");
-        } else if (!i.hasExtra(EXTRA_APPID)) {
-            Log.e(TAG, "No application ID in AppDetails!?");
-        } else {
-            appId = i.getStringExtra(EXTRA_APPID);
+        if (!i.hasExtra(EXTRA_APPID)) {
+            Log.e(TAG, "No application ID found in the intent!");
+            return null;
         }
-        return appId;
+        return i.getStringExtra(EXTRA_APPID);
     }
 
     @Override
diff --git a/F-Droid/src/org/fdroid/fdroid/FDroid.java b/F-Droid/src/org/fdroid/fdroid/FDroid.java
index dcfb37012..79abafece 100644
--- a/F-Droid/src/org/fdroid/fdroid/FDroid.java
+++ b/F-Droid/src/org/fdroid/fdroid/FDroid.java
@@ -22,6 +22,7 @@ package org.fdroid.fdroid;
 import android.app.AlertDialog;
 import android.app.AlertDialog.Builder;
 import android.app.NotificationManager;
+import android.app.SearchManager;
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -55,11 +56,10 @@ public class FDroid extends ActionBarActivity {
 
     private static final String TAG = "fdroid.FDroid";
 
-    public static final int REQUEST_APPDETAILS = 0;
-    public static final int REQUEST_MANAGEREPOS = 1;
-    public static final int REQUEST_PREFS = 2;
-    public static final int REQUEST_ENABLE_BLUETOOTH = 3;
-    public static final int REQUEST_SWAP = 4;
+    public static final int REQUEST_MANAGEREPOS = 0;
+    public static final int REQUEST_PREFS = 1;
+    public static final int REQUEST_ENABLE_BLUETOOTH = 2;
+    public static final int REQUEST_SWAP = 3;
 
     public static final String EXTRA_TAB_UPDATE = "extraTab";
 
@@ -86,25 +86,18 @@ public class FDroid extends ActionBarActivity {
         // Start a search by just typing
         setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
 
-        Intent i = getIntent();
-        Uri data = i.getData();
-        String appid = null;
-        if (data != null) {
-            if (data.isHierarchical()) {
-                // http(s)://f-droid.org/repository/browse?fdid=app.id
-                appid = data.getQueryParameter("fdid");
-            }
-        } else if (i.hasExtra(EXTRA_TAB_UPDATE)) {
-            boolean showUpdateTab = i.getBooleanExtra(EXTRA_TAB_UPDATE, false);
+        Intent intent = getIntent();
+
+        // If the intent can be handled via AppDetails or SearchResults, it
+        // will call finish() and the rest of the code won't execute
+        handleIntent(intent);
+
+        if (intent.hasExtra(EXTRA_TAB_UPDATE)) {
+            boolean showUpdateTab = intent.getBooleanExtra(EXTRA_TAB_UPDATE, false);
             if (showUpdateTab) {
                 getTabManager().selectTab(2);
             }
         }
-        if (appid != null && appid.length() > 0) {
-            Intent call = new Intent(this, AppDetails.class);
-            call.putExtra(AppDetails.EXTRA_APPID, appid);
-            startActivityForResult(call, REQUEST_APPDETAILS);
-        }
 
         Uri uri = AppProvider.getContentUri();
         getContentResolver().registerContentObserver(uri, true, new AppObserver());
@@ -118,17 +111,95 @@ public class FDroid extends ActionBarActivity {
         checkForAddRepoIntent();
     }
 
+    private void handleIntent(Intent intent) {
+        final Uri data = intent.getData();
+        if (data == null) {
+            return;
+        }
+        final String scheme = data.getScheme();
+        final String path = data.getPath();
+        String appId = null;
+        String query = null;
+        if (data.isHierarchical()) {
+            final String host = data.getHost();
+            if (host == null) {
+                return;
+            }
+            switch (host) {
+            case "f-droid.org":
+                // http://f-droid.org/app/app.id
+                if (path.startsWith("/repository/browse")) {
+                    // http://f-droid.org/repository/browse?fdid=app.id
+                    appId = data.getQueryParameter("fdid");
+                } else if (path.startsWith("/app")) {
+                    appId = data.getLastPathSegment();
+                    if (appId != null && appId.equals("app")) {
+                        appId = null;
+                    }
+                }
+                break;
+            case "details":
+                // market://details?id=app.id
+                appId = data.getQueryParameter("id");
+                break;
+            case "search":
+                // market://search?q=query
+                query = data.getQueryParameter("q");
+                break;
+            case "play.google.com":
+                if (path.startsWith("/store/apps/details")) {
+                    // http://play.google.com/store/apps/details?id=app.id
+                    appId = data.getQueryParameter("id");
+                } else if (path.startsWith("/store/search")) {
+                    // http://play.google.com/store/search?q=foo
+                    query = data.getQueryParameter("q");
+                }
+                break;
+            case "apps":
+            case "amazon.com":
+            case "www.amazon.com":
+                // amzn://apps/android?p=app.id
+                // http://amazon.com/gp/mas/dl/android?p=app.id
+                appId = data.getQueryParameter("p");
+                break;
+            }
+        } else if (scheme.equals("fdroid.app")) {
+            // fdroid.app:app.id
+            appId = data.getSchemeSpecificPart();
+        } else if (scheme.equals("fdroid.search")) {
+            // fdroid.search:query
+            query = data.getSchemeSpecificPart();
+        }
+
+        Intent call = null;
+        if (appId != null && appId.length() > 0) {
+            Log.d(TAG, "FDroid launched via app link for '" + appId + "'");
+            call = new Intent(this, AppDetails.class);
+            call.putExtra(AppDetails.EXTRA_APPID, appId);
+        } else if (query != null && query.length() > 0) {
+            Log.d(TAG, "FDroid launched via search link for '" + query + "'");
+            call = new Intent(this, SearchResults.class);
+            call.setAction(Intent.ACTION_SEARCH);
+            call.putExtra(SearchManager.QUERY, query);
+        }
+        if (call != null) {
+            startActivity(call);
+            finish();
+        }
+    }
+
     private void checkForAddRepoIntent() {
         // Don't handle the intent after coming back to this view (e.g. after hitting the back button)
         // http://stackoverflow.com/a/14820849
-        if (!getIntent().hasExtra("handled")) {
-            NewRepoConfig parser = new NewRepoConfig(this, getIntent());
+        Intent intent = getIntent();
+        if (intent.hasExtra("handled")) {
+            NewRepoConfig parser = new NewRepoConfig(this, intent);
             if (parser.isValidRepo()) {
-                getIntent().putExtra("handled", true);
+                intent.putExtra("handled", true);
                 if (parser.isFromSwap()) {
-                    startActivityForResult(new Intent(ACTION_ADD_REPO, getIntent().getData(), this, ConnectSwapActivity.class), REQUEST_SWAP);
+                    startActivityForResult(new Intent(ACTION_ADD_REPO, intent.getData(), this, ConnectSwapActivity.class), REQUEST_SWAP);
                 } else {
-                    startActivity(new Intent(ACTION_ADD_REPO, getIntent().getData(), this, ManageReposActivity.class));
+                    startActivity(new Intent(ACTION_ADD_REPO, intent.getData(), this, ManageReposActivity.class));
                 }
             } else if (parser.getErrorMessage() != null) {
                 Toast.makeText(this, parser.getErrorMessage(), Toast.LENGTH_LONG).show();
@@ -251,8 +322,6 @@ public class FDroid extends ActionBarActivity {
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 
         switch (requestCode) {
-        case REQUEST_APPDETAILS:
-            break;
         case REQUEST_MANAGEREPOS:
             if (data != null && data.hasExtra(ManageReposActivity.REQUEST_UPDATE)) {
                 AlertDialog.Builder ask_alrt = new AlertDialog.Builder(this);
diff --git a/F-Droid/src/org/fdroid/fdroid/SearchResults.java b/F-Droid/src/org/fdroid/fdroid/SearchResults.java
index 9c287ff00..76453bf07 100644
--- a/F-Droid/src/org/fdroid/fdroid/SearchResults.java
+++ b/F-Droid/src/org/fdroid/fdroid/SearchResults.java
@@ -75,7 +75,6 @@ public class SearchResults extends ActionBarActivity {
 
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
-
         super.onCreateOptionsMenu(menu);
         MenuItem search = menu.add(Menu.NONE, SEARCH, 1, R.string.menu_search).setIcon(
                 android.R.drawable.ic_menu_search);
@@ -85,7 +84,6 @@ public class SearchResults extends ActionBarActivity {
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
-
         switch (item.getItemId()) {
 
         case android.R.id.home:
diff --git a/F-Droid/src/org/fdroid/fdroid/views/fragments/AppListFragment.java b/F-Droid/src/org/fdroid/fdroid/views/fragments/AppListFragment.java
index a34d5994c..682823626 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/fragments/AppListFragment.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/fragments/AppListFragment.java
@@ -17,7 +17,6 @@ import android.widget.AdapterView;
 import android.widget.TextView;
 
 import org.fdroid.fdroid.AppDetails;
-import org.fdroid.fdroid.FDroid;
 import org.fdroid.fdroid.Preferences;
 import org.fdroid.fdroid.R;
 import org.fdroid.fdroid.UpdateService;
@@ -32,6 +31,8 @@ abstract public class AppListFragment extends ThemeableListFragment implements
 
     private static final String TAG = "fdroid.AppListFragment";
 
+    private static final int REQUEST_APPDETAILS = 0;
+
     public static final String[] APP_PROJECTION = {
             AppProvider.DataColumns._ID, // Required for cursor loader to work.
             AppProvider.DataColumns.APP_ID,
@@ -149,7 +150,7 @@ abstract public class AppListFragment extends ThemeableListFragment implements
             Intent intent = getAppDetailsIntent();
             intent.putExtra(AppDetails.EXTRA_APPID, app.id);
             intent.putExtra(AppDetails.EXTRA_FROM, getFromTitle());
-            startActivityForResult(intent, FDroid.REQUEST_APPDETAILS);
+            startActivityForResult(intent, REQUEST_APPDETAILS);
         }
     }
 
diff --git a/F-Droid/src/org/fdroid/fdroid/views/fragments/SearchResultsFragment.java b/F-Droid/src/org/fdroid/fdroid/views/fragments/SearchResultsFragment.java
index 5ccb0f4bc..648d7adcb 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/fragments/SearchResultsFragment.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/fragments/SearchResultsFragment.java
@@ -36,21 +36,6 @@ public class SearchResultsFragment extends ListFragment implements LoaderManager
         String query = null;
         if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
             query = intent.getStringExtra(SearchManager.QUERY);
-        } else {
-            final Uri data = intent.getData();
-            if (data == null) {
-                return "";
-            }
-            if (data.isHierarchical()) {
-                // market://search?q=foo
-                // https://play.google.com/store/search?q=foo
-                query = data.getQueryParameter("q");
-                if (query != null && query.startsWith("pname:"))
-                    query = query.substring(6);
-            } else {
-                // fdroid.search:foo
-                query = data.getEncodedSchemeSpecificPart();
-            }
         }
         if (query == null) {
             return "";
@@ -121,8 +106,7 @@ public class SearchResultsFragment extends ListFragment implements LoaderManager
 
     @Override
     public void onListItemClick(ListView l, View v, int position, long id) {
-        final App app;
-        app = new App((Cursor) adapter.getItem(position));
+        final App app = new App((Cursor) adapter.getItem(position));
 
         Intent intent = new Intent(getActivity(), AppDetails.class);
         intent.putExtra(AppDetails.EXTRA_APPID, app.id);