diff --git a/app/src/full/java/org/fdroid/fdroid/localrepo/TreeUriScannerIntentService.java b/app/src/full/java/org/fdroid/fdroid/localrepo/TreeUriScannerIntentService.java index 0c90fcd2f..33eb0374c 100644 --- a/app/src/full/java/org/fdroid/fdroid/localrepo/TreeUriScannerIntentService.java +++ b/app/src/full/java/org/fdroid/fdroid/localrepo/TreeUriScannerIntentService.java @@ -29,13 +29,13 @@ import android.support.v4.provider.DocumentFile; import android.util.Log; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; +import org.fdroid.fdroid.AddRepoIntentService; import org.fdroid.fdroid.IndexUpdater; import org.fdroid.fdroid.IndexV1Updater; import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; -import org.fdroid.fdroid.views.main.MainActivity; import java.io.File; import java.io.IOException; @@ -159,16 +159,7 @@ public class TreeUriScannerIntentService extends IntentService { } } - Intent intent = new Intent(context, MainActivity.class); - intent.setAction(Intent.ACTION_VIEW); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.setData(repoUri.buildUpon() - .appendQueryParameter("fingerprint", fingerprint) - .build()); - context.startActivity(intent); - // TODO parse repo URL/mirrors/fingerprint using Jackson - // https://stackoverflow.com/questions/24835431/use-jackson-to-stream-parse-an-array-of-json-objects# + AddRepoIntentService.addRepo(context, repoUri, fingerprint); // TODO rework IndexUpdater.getSigningCertFromJar to work for here - // TODO check whether fingerprint is already in the database } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d11fd12df..c255a38dd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -271,6 +271,9 @@ android:name=".data.InstalledAppProviderService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false"/> + diff --git a/app/src/main/java/org/fdroid/fdroid/AddRepoIntentService.java b/app/src/main/java/org/fdroid/fdroid/AddRepoIntentService.java new file mode 100644 index 000000000..37ce63101 --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/AddRepoIntentService.java @@ -0,0 +1,145 @@ +package org.fdroid.fdroid; + +import android.app.IntentService; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; +import org.fdroid.fdroid.data.Repo; +import org.fdroid.fdroid.data.RepoProvider; +import org.fdroid.fdroid.views.main.MainActivity; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Locale; + +/** + * Handles requests to add new repos via URLs. This is an {@code IntentService} + * so that requests are queued, which is necessary when either + * {@link org.fdroid.fdroid.localrepo.TreeUriScannerIntentService} or + * {@link org.fdroid.fdroid.localrepo.SDCardScannerService} finds multiple + * repos on a disk. This should hopefully also serve as the beginnings of + * a new architecture for handling these requests. This does all the + * processing first, up front, then only launches UI as needed. + * {@link org.fdroid.fdroid.views.ManageReposActivity} currently does the + * opposite. + *

+ * This only really properly queues {@link Intent}s that get filtered out. The + * {@code Intent}s that go on to {@code ManageReposActivity} will not wait + * until for that {@code Activity} to be ready to handle the next. So when + * multiple mirrors are discovered at once, only one in that session will + * likely be added. + */ +public class AddRepoIntentService extends IntentService { + public static final String TAG = "AddRepoIntentService"; + + private static final String ACTION_ADD_REPO = "org.fdroid.fdroid.action.ADD_REPO"; + + public AddRepoIntentService() { + super("AddRepoIntentService"); + } + + public static void addRepo(Context context, @NonNull Uri repoUri, @Nullable String fingerprint) { + Intent intent = new Intent(context, AddRepoIntentService.class); + intent.setAction(ACTION_ADD_REPO); + if (TextUtils.isEmpty(fingerprint)) { + intent.setData(repoUri); + } else { + intent.setData(repoUri.buildUpon() + .appendQueryParameter("fingerprint", fingerprint) + .build()); + } + context.startService(intent); + } + + @Override + protected void onHandleIntent(Intent intent) { + android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST); + if (intent == null || intent.getData() == null) { + return; + } + + Uri uri = intent.getData(); + String urlString; + try { + urlString = normalizeUrl(uri); + } catch (URISyntaxException e) { + Log.i(TAG, e.getLocalizedMessage()); + return; + } + + String fingerprint = uri.getQueryParameter("fingerprint"); + for (Repo repo : RepoProvider.Helper.all(this)) { + if (repo.inuse && TextUtils.equals(fingerprint, repo.fingerprint)) { + if (TextUtils.equals(urlString, repo.address)) { + Utils.debugLog(TAG, urlString + " already added as a repo"); + return; + } else { + for (String mirrorUrl : repo.getMirrorList()) { + if (urlString.startsWith(mirrorUrl)) { + Utils.debugLog(TAG, urlString + " already added as a mirror"); + return; + } + } + } + } + } + intent.setComponent(new ComponentName(this, MainActivity.class)); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + + /** + * Some basic sanitization of URLs, so that two URLs which have the same semantic meaning + * are represented by the exact same string by F-Droid. This will help to make sure that, + * e.g. "http://10.0.1.50" and "http://10.0.1.50/" are not two different repositories. + *

+ * Currently it normalizes the path so that "/./" are removed and "test/../" is collapsed. + * This is done using {@link URI#normalize()}. It also removes multiple consecutive forward + * slashes in the path and replaces them with one. Finally, it removes trailing slashes. + *

+ * {@code content://} URLs used for repos stored on removable storage get messed up by + * {@link URI}. + */ + public static String normalizeUrl(String urlString) throws URISyntaxException { + if (TextUtils.isEmpty(urlString)) { + return null; + } + return normalizeUrl(Uri.parse(urlString)); + } + + public static String normalizeUrl(Uri uri) throws URISyntaxException { + if (!uri.isAbsolute()) { + throw new URISyntaxException(uri.toString(), "Must provide an absolute URI for repositories"); + } + if (!uri.isHierarchical()) { + throw new URISyntaxException(uri.toString(), "Must provide an hierarchical URI for repositories"); + } + if ("content".equals(uri.getScheme())) { + return uri.toString(); + } + String path = uri.getPath(); + if (path != null) { + path = path.replaceAll("//*/", "/"); // Collapse multiple forward slashes into 1. + if (path.length() > 0 && path.charAt(path.length() - 1) == '/') { + path = path.substring(0, path.length() - 1); + } + } + String scheme = uri.getScheme(); + String host = uri.getHost(); + if (TextUtils.isEmpty(scheme) || TextUtils.isEmpty(host)) { + return uri.toString(); + } + return new URI(scheme.toLowerCase(Locale.ENGLISH), + uri.getUserInfo(), + host.toLowerCase(Locale.ENGLISH), + uri.getPort(), + path, + uri.getQuery(), + uri.getFragment()).normalize().toString(); + } +} diff --git a/app/src/main/java/org/fdroid/fdroid/views/ManageReposActivity.java b/app/src/main/java/org/fdroid/fdroid/views/ManageReposActivity.java index 012fffd16..2f8cdb777 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/ManageReposActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/ManageReposActivity.java @@ -33,6 +33,7 @@ import android.net.Uri; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.LoaderManager; @@ -54,6 +55,7 @@ import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; +import org.fdroid.fdroid.AddRepoIntentService; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.IndexUpdater; import org.fdroid.fdroid.R; @@ -69,7 +71,6 @@ import java.io.File; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; -import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Arrays; @@ -83,7 +84,7 @@ public class ManageReposActivity extends AppCompatActivity private static final String DEFAULT_NEW_REPO_TEXT = "https://"; private enum AddRepoState { - DOESNT_EXIST, EXISTS_FINGERPRINT_MISMATCH, EXISTS_ADD_MIRROR, + DOESNT_EXIST, EXISTS_FINGERPRINT_MISMATCH, EXISTS_ADD_MIRROR, EXISTS_ALREADY_MIRROR, EXISTS_DISABLED, EXISTS_ENABLED, EXISTS_UPGRADABLE_TO_SIGNED, INVALID_URL, IS_SWAP } @@ -312,7 +313,7 @@ public class ManageReposActivity extends AppCompatActivity String url = uriEditText.getText().toString(); try { - url = normalizeUrl(url); + url = AddRepoIntentService.normalizeUrl(url); } catch (URISyntaxException e) { invalidUrl(); return; @@ -416,7 +417,7 @@ public class ManageReposActivity extends AppCompatActivity private void validateRepoDetails(@NonNull String uri, @NonNull String fingerprint) { try { - uri = normalizeUrl(uri); + uri = AddRepoIntentService.normalizeUrl(uri); } catch (URISyntaxException e) { // Don't bother dealing with this exception yet, as this is called every time // a letter is added to the repo URL text input. We don't want to display a message @@ -438,8 +439,9 @@ public class ManageReposActivity extends AppCompatActivity } else if (repo.fingerprint != null && !repo.fingerprint.equalsIgnoreCase(fingerprint)) { repoFingerprintDoesntMatch(repo); } else { - if (!TextUtils.equals(repo.address, uri) - && !repo.getMirrorList().contains(uri)) { + if (repo.getMirrorList().contains(uri) && !TextUtils.equals(repo.address, uri) && repo.inuse) { + repoExistsAlreadyMirror(repo); + } else if (!TextUtils.equals(repo.address, uri) && repo.inuse) { repoExistsAddMirror(repo); } else if (repo.inuse) { repoExistsAndEnabled(repo); @@ -488,6 +490,10 @@ public class ManageReposActivity extends AppCompatActivity R.string.repo_add_mirror, true); } + private void repoExistsAlreadyMirror(Repo repo) { + updateUi(repo, AddRepoState.EXISTS_ALREADY_MIRROR, 0, false, R.string.ok, true); + } + private void upgradingToSigned(Repo repo) { updateUi(repo, AddRepoState.EXISTS_UPGRADABLE_TO_SIGNED, R.string.repo_exists_add_fingerprint, false, R.string.add_key, true); @@ -519,6 +525,13 @@ public class ManageReposActivity extends AppCompatActivity addButton.setText(addTextRes); addButton.setEnabled(addEnabled); + + if (Build.VERSION.SDK_INT >= 15 && addRepoState == AddRepoState.EXISTS_ALREADY_MIRROR) { + addButton.callOnClick(); + editRepo(repo); + String msg = getString(R.string.repo_exists_and_enabled, repo.address); + Toast.makeText(context, msg, Toast.LENGTH_LONG).show(); + } } } @@ -690,53 +703,6 @@ public class ManageReposActivity extends AppCompatActivity checker.execute(originalAddress); } - /** - * Some basic sanitization of URLs, so that two URLs which have the same semantic meaning - * are represented by the exact same string by F-Droid. This will help to make sure that, - * e.g. "http://10.0.1.50" and "http://10.0.1.50/" are not two different repositories. - *

- * Currently it normalizes the path so that "/./" are removed and "test/../" is collapsed. - * This is done using {@link URI#normalize()}. It also removes multiple consecutive forward - * slashes in the path and replaces them with one. Finally, it removes trailing slashes. - *

- * {@code content://} URLs used for repos stored on removable storage get messed up by - * {@link URI}. - */ - private String normalizeUrl(String urlString) throws URISyntaxException { - if (urlString == null) { - return null; - } - Uri uri = Uri.parse(urlString); - if (!uri.isAbsolute()) { - throw new URISyntaxException(urlString, "Must provide an absolute URI for repositories"); - } - if (!uri.isHierarchical()) { - throw new URISyntaxException(urlString, "Must provide an hierarchical URI for repositories"); - } - if ("content".equals(uri.getScheme())) { - return uri.toString(); - } - String path = uri.getPath(); - if (path != null) { - path = path.replaceAll("//*/", "/"); // Collapse multiple forward slashes into 1. - if (path.length() > 0 && path.charAt(path.length() - 1) == '/') { - path = path.substring(0, path.length() - 1); - } - } - String scheme = uri.getScheme(); - String host = uri.getHost(); - if (TextUtils.isEmpty(scheme) || TextUtils.isEmpty(host)) { - return urlString; - } - return new URI(scheme.toLowerCase(Locale.ENGLISH), - uri.getUserInfo(), - host.toLowerCase(Locale.ENGLISH), - uri.getPort(), - path, - uri.getQuery(), - uri.getFragment()).normalize().toString(); - } - /** * Create a repository without a username or password. */ @@ -747,7 +713,7 @@ public class ManageReposActivity extends AppCompatActivity private void createNewRepo(String address, String fingerprint, final String username, final String password) { try { - address = normalizeUrl(address); + address = AddRepoIntentService.normalizeUrl(address); } catch (URISyntaxException e) { // Leave address as it was. }