Merge branch 'clean-up-repo-and-downloader' into 'master'
expose mirror options; Clean up Repo and Downloader See merge request fdroid/fdroidclient!663
This commit is contained in:
		
						commit
						5f2b053b1c
					
				@ -180,7 +180,7 @@ def preDexEnabled = "true".equals(System.getProperty("pre-dex", "true"))
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
android {
 | 
					android {
 | 
				
			||||||
    compileSdkVersion 24
 | 
					    compileSdkVersion 24
 | 
				
			||||||
    buildToolsVersion '25.0.2'
 | 
					    buildToolsVersion '25.0.3'
 | 
				
			||||||
    useLibrary 'org.apache.http.legacy'
 | 
					    useLibrary 'org.apache.http.legacy'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    buildTypes {
 | 
					    buildTypes {
 | 
				
			||||||
 | 
				
			|||||||
@ -60,12 +60,8 @@ import org.fdroid.fdroid.installer.InstallHistoryService;
 | 
				
			|||||||
import org.fdroid.fdroid.net.ImageLoaderForUIL;
 | 
					import org.fdroid.fdroid.net.ImageLoaderForUIL;
 | 
				
			||||||
import org.fdroid.fdroid.net.WifiStateChangeService;
 | 
					import org.fdroid.fdroid.net.WifiStateChangeService;
 | 
				
			||||||
import org.fdroid.fdroid.views.hiding.HidingManager;
 | 
					import org.fdroid.fdroid.views.hiding.HidingManager;
 | 
				
			||||||
import sun.net.www.protocol.bluetooth.Handler;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.net.URL;
 | 
					 | 
				
			||||||
import java.net.URLStreamHandler;
 | 
					 | 
				
			||||||
import java.net.URLStreamHandlerFactory;
 | 
					 | 
				
			||||||
import java.security.Security;
 | 
					import java.security.Security;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -370,16 +366,6 @@ public class FDroidApp extends Application {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // This is added so that the bluetooth:// scheme we use for URLs the BluetoothDownloader
 | 
					 | 
				
			||||||
        // understands is not treated as invalid by the java.net.URL class. The actual Handler does
 | 
					 | 
				
			||||||
        // nothing, but its presence is enough.
 | 
					 | 
				
			||||||
        URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {
 | 
					 | 
				
			||||||
            @Override
 | 
					 | 
				
			||||||
            public URLStreamHandler createURLStreamHandler(String protocol) {
 | 
					 | 
				
			||||||
                return TextUtils.equals(protocol, "bluetooth") ? new Handler() : null;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final Context context = this;
 | 
					        final Context context = this;
 | 
				
			||||||
        Preferences.get().registerUnstableUpdatesChangeListener(new Preferences.ChangeListener() {
 | 
					        Preferences.get().registerUnstableUpdatesChangeListener(new Preferences.ChangeListener() {
 | 
				
			||||||
            @Override
 | 
					            @Override
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,6 @@ import android.net.Uri;
 | 
				
			|||||||
import android.support.annotation.NonNull;
 | 
					import android.support.annotation.NonNull;
 | 
				
			||||||
import android.text.TextUtils;
 | 
					import android.text.TextUtils;
 | 
				
			||||||
import android.util.Log;
 | 
					import android.util.Log;
 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
 | 
					import com.fasterxml.jackson.annotation.JsonAutoDetect;
 | 
				
			||||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
 | 
					import com.fasterxml.jackson.annotation.PropertyAccessor;
 | 
				
			||||||
import com.fasterxml.jackson.core.JsonFactory;
 | 
					import com.fasterxml.jackson.core.JsonFactory;
 | 
				
			||||||
@ -15,7 +14,6 @@ import com.fasterxml.jackson.core.type.TypeReference;
 | 
				
			|||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
 | 
					import com.fasterxml.jackson.databind.DeserializationFeature;
 | 
				
			||||||
import com.fasterxml.jackson.databind.InjectableValues;
 | 
					import com.fasterxml.jackson.databind.InjectableValues;
 | 
				
			||||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
					import com.fasterxml.jackson.databind.ObjectMapper;
 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.apache.commons.io.FileUtils;
 | 
					import org.apache.commons.io.FileUtils;
 | 
				
			||||||
import org.fdroid.fdroid.data.Apk;
 | 
					import org.fdroid.fdroid.data.Apk;
 | 
				
			||||||
import org.fdroid.fdroid.data.App;
 | 
					import org.fdroid.fdroid.data.App;
 | 
				
			||||||
@ -31,7 +29,6 @@ import java.io.IOException;
 | 
				
			|||||||
import java.io.InputStream;
 | 
					import java.io.InputStream;
 | 
				
			||||||
import java.net.ConnectException;
 | 
					import java.net.ConnectException;
 | 
				
			||||||
import java.net.SocketTimeoutException;
 | 
					import java.net.SocketTimeoutException;
 | 
				
			||||||
import java.net.URL;
 | 
					 | 
				
			||||||
import java.security.cert.X509Certificate;
 | 
					import java.security.cert.X509Certificate;
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
import java.util.Date;
 | 
					import java.util.Date;
 | 
				
			||||||
@ -135,7 +132,7 @@ public class IndexV1Updater extends RepoUpdater {
 | 
				
			|||||||
                    if (downloader != null) {
 | 
					                    if (downloader != null) {
 | 
				
			||||||
                        FileUtils.deleteQuietly(downloader.outputFile);
 | 
					                        FileUtils.deleteQuietly(downloader.outputFile);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    throw new RepoUpdater.UpdateException(repo, "Error getting index file", e2);
 | 
					                    throw new RepoUpdater.UpdateException("Error getting index file", e2);
 | 
				
			||||||
                } catch (InterruptedException e2) {
 | 
					                } catch (InterruptedException e2) {
 | 
				
			||||||
                    // ignored if canceled, the local database just won't be updated
 | 
					                    // ignored if canceled, the local database just won't be updated
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -144,7 +141,7 @@ public class IndexV1Updater extends RepoUpdater {
 | 
				
			|||||||
            if (downloader != null) {
 | 
					            if (downloader != null) {
 | 
				
			||||||
                FileUtils.deleteQuietly(downloader.outputFile);
 | 
					                FileUtils.deleteQuietly(downloader.outputFile);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            throw new RepoUpdater.UpdateException(repo, "Error getting index file", e);
 | 
					            throw new RepoUpdater.UpdateException("Error getting index file", e);
 | 
				
			||||||
        } catch (InterruptedException e) {
 | 
					        } catch (InterruptedException e) {
 | 
				
			||||||
            // ignored if canceled, the local database just won't be updated
 | 
					            // ignored if canceled, the local database just won't be updated
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -157,7 +154,7 @@ public class IndexV1Updater extends RepoUpdater {
 | 
				
			|||||||
        JarFile jarFile = new JarFile(outputFile, true);
 | 
					        JarFile jarFile = new JarFile(outputFile, true);
 | 
				
			||||||
        JarEntry indexEntry = (JarEntry) jarFile.getEntry(DATA_FILE_NAME);
 | 
					        JarEntry indexEntry = (JarEntry) jarFile.getEntry(DATA_FILE_NAME);
 | 
				
			||||||
        InputStream indexInputStream = new ProgressBufferedInputStream(jarFile.getInputStream(indexEntry),
 | 
					        InputStream indexInputStream = new ProgressBufferedInputStream(jarFile.getInputStream(indexEntry),
 | 
				
			||||||
                processIndexListener, new URL(repo.address), (int) indexEntry.getSize());
 | 
					                processIndexListener, repo.address, (int) indexEntry.getSize());
 | 
				
			||||||
        processIndexV1(indexInputStream, indexEntry, cacheTag);
 | 
					        processIndexV1(indexInputStream, indexEntry, cacheTag);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -236,7 +233,7 @@ public class IndexV1Updater extends RepoUpdater {
 | 
				
			|||||||
        long timestamp = (Long) repoMap.get("timestamp") / 1000;
 | 
					        long timestamp = (Long) repoMap.get("timestamp") / 1000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (repo.timestamp > timestamp) {
 | 
					        if (repo.timestamp > timestamp) {
 | 
				
			||||||
            throw new RepoUpdater.UpdateException(repo, "index.jar is older that current index! "
 | 
					            throw new RepoUpdater.UpdateException("index.jar is older that current index! "
 | 
				
			||||||
                    + timestamp + " < " + repo.timestamp);
 | 
					                    + timestamp + " < " + repo.timestamp);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -410,16 +407,14 @@ public class IndexV1Updater extends RepoUpdater {
 | 
				
			|||||||
        String certFromJar = Hasher.hex(rawCertFromJar);
 | 
					        String certFromJar = Hasher.hex(rawCertFromJar);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (TextUtils.isEmpty(certFromJar)) {
 | 
					        if (TextUtils.isEmpty(certFromJar)) {
 | 
				
			||||||
            throw new SigningException(repo,
 | 
					            throw new SigningException(SIGNED_FILE_NAME + " must have an included signing certificate!");
 | 
				
			||||||
                    SIGNED_FILE_NAME + " must have an included signing certificate!");
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (repo.signingCertificate == null) {
 | 
					        if (repo.signingCertificate == null) {
 | 
				
			||||||
            if (repo.fingerprint != null) {
 | 
					            if (repo.fingerprint != null) {
 | 
				
			||||||
                String fingerprintFromJar = Utils.calcFingerprint(rawCertFromJar);
 | 
					                String fingerprintFromJar = Utils.calcFingerprint(rawCertFromJar);
 | 
				
			||||||
                if (!repo.fingerprint.equalsIgnoreCase(fingerprintFromJar)) {
 | 
					                if (!repo.fingerprint.equalsIgnoreCase(fingerprintFromJar)) {
 | 
				
			||||||
                    throw new SigningException(repo,
 | 
					                    throw new SigningException("Supplied certificate fingerprint does not match!");
 | 
				
			||||||
                            "Supplied certificate fingerprint does not match!");
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Utils.debugLog(TAG, "Saving new signing certificate to database for " + repo.address);
 | 
					            Utils.debugLog(TAG, "Saving new signing certificate to database for " + repo.address);
 | 
				
			||||||
@ -431,14 +426,14 @@ public class IndexV1Updater extends RepoUpdater {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (TextUtils.isEmpty(repo.signingCertificate)) {
 | 
					        if (TextUtils.isEmpty(repo.signingCertificate)) {
 | 
				
			||||||
            throw new SigningException(repo, "A empty repo signing certificate is invalid!");
 | 
					            throw new SigningException("A empty repo signing certificate is invalid!");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (repo.signingCertificate.equals(certFromJar)) {
 | 
					        if (repo.signingCertificate.equals(certFromJar)) {
 | 
				
			||||||
            return; // we have a match!
 | 
					            return; // we have a match!
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        throw new SigningException(repo, "Signing certificate does not match!");
 | 
					        throw new SigningException("Signing certificate does not match!");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -3,12 +3,11 @@ package org.fdroid.fdroid;
 | 
				
			|||||||
import java.io.BufferedInputStream;
 | 
					import java.io.BufferedInputStream;
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.io.InputStream;
 | 
					import java.io.InputStream;
 | 
				
			||||||
import java.net.URL;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ProgressBufferedInputStream extends BufferedInputStream {
 | 
					class ProgressBufferedInputStream extends BufferedInputStream {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final ProgressListener progressListener;
 | 
					    private final ProgressListener progressListener;
 | 
				
			||||||
    private final URL sourceUrl;
 | 
					    private final String urlString;
 | 
				
			||||||
    private final int totalBytes;
 | 
					    private final int totalBytes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private int currentBytes;
 | 
					    private int currentBytes;
 | 
				
			||||||
@ -17,10 +16,10 @@ class ProgressBufferedInputStream extends BufferedInputStream {
 | 
				
			|||||||
     * Reports progress to the specified {@link ProgressListener}, with the
 | 
					     * Reports progress to the specified {@link ProgressListener}, with the
 | 
				
			||||||
     * progress based on the {@code totalBytes}.
 | 
					     * progress based on the {@code totalBytes}.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    ProgressBufferedInputStream(InputStream in, ProgressListener progressListener, URL sourceUrl, int totalBytes) {
 | 
					    ProgressBufferedInputStream(InputStream in, ProgressListener progressListener, String urlString, int totalBytes) {
 | 
				
			||||||
        super(in);
 | 
					        super(in);
 | 
				
			||||||
        this.progressListener = progressListener;
 | 
					        this.progressListener = progressListener;
 | 
				
			||||||
        this.sourceUrl = sourceUrl;
 | 
					        this.urlString = urlString;
 | 
				
			||||||
        this.totalBytes = totalBytes;
 | 
					        this.totalBytes = totalBytes;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -32,7 +31,7 @@ class ProgressBufferedInputStream extends BufferedInputStream {
 | 
				
			|||||||
             * the digits changing because it looks pretty, < 9000 since the reads won't
 | 
					             * the digits changing because it looks pretty, < 9000 since the reads won't
 | 
				
			||||||
             * line up exactly */
 | 
					             * line up exactly */
 | 
				
			||||||
            if (currentBytes % 333333 < 9000) {
 | 
					            if (currentBytes % 333333 < 9000) {
 | 
				
			||||||
                progressListener.onProgress(sourceUrl, currentBytes, totalBytes);
 | 
					                progressListener.onProgress(urlString, currentBytes, totalBytes);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return super.read(buffer, byteOffset, byteCount);
 | 
					        return super.read(buffer, byteOffset, byteCount);
 | 
				
			||||||
 | 
				
			|||||||
@ -19,6 +19,6 @@ import java.net.URL;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public interface ProgressListener {
 | 
					public interface ProgressListener {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void onProgress(URL sourceUrl, int bytesRead, int totalBytes);
 | 
					    void onProgress(String urlString, long bytesRead, long totalBytes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -53,7 +53,6 @@ import javax.xml.parsers.SAXParserFactory;
 | 
				
			|||||||
import java.io.File;
 | 
					import java.io.File;
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.io.InputStream;
 | 
					import java.io.InputStream;
 | 
				
			||||||
import java.net.URL;
 | 
					 | 
				
			||||||
import java.security.CodeSigner;
 | 
					import java.security.CodeSigner;
 | 
				
			||||||
import java.security.cert.Certificate;
 | 
					import java.security.cert.Certificate;
 | 
				
			||||||
import java.security.cert.X509Certificate;
 | 
					import java.security.cert.X509Certificate;
 | 
				
			||||||
@ -139,7 +138,7 @@ public class RepoUpdater {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            throw new UpdateException(repo, "Error getting index file", e);
 | 
					            throw new UpdateException("Error getting index file", e);
 | 
				
			||||||
        } catch (InterruptedException e) {
 | 
					        } catch (InterruptedException e) {
 | 
				
			||||||
            // ignored if canceled, the local database just won't be updated
 | 
					            // ignored if canceled, the local database just won't be updated
 | 
				
			||||||
            e.printStackTrace();
 | 
					            e.printStackTrace();
 | 
				
			||||||
@ -202,7 +201,7 @@ public class RepoUpdater {
 | 
				
			|||||||
        InputStream indexInputStream = null;
 | 
					        InputStream indexInputStream = null;
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            if (downloadedFile == null || !downloadedFile.exists()) {
 | 
					            if (downloadedFile == null || !downloadedFile.exists()) {
 | 
				
			||||||
                throw new UpdateException(repo, downloadedFile + " does not exist!");
 | 
					                throw new UpdateException(downloadedFile + " does not exist!");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Due to a bug in Android 5.0 Lollipop, the inclusion of spongycastle causes
 | 
					            // Due to a bug in Android 5.0 Lollipop, the inclusion of spongycastle causes
 | 
				
			||||||
@ -213,7 +212,7 @@ public class RepoUpdater {
 | 
				
			|||||||
            JarFile jarFile = new JarFile(downloadedFile, true);
 | 
					            JarFile jarFile = new JarFile(downloadedFile, true);
 | 
				
			||||||
            JarEntry indexEntry = (JarEntry) jarFile.getEntry("index.xml");
 | 
					            JarEntry indexEntry = (JarEntry) jarFile.getEntry("index.xml");
 | 
				
			||||||
            indexInputStream = new ProgressBufferedInputStream(jarFile.getInputStream(indexEntry),
 | 
					            indexInputStream = new ProgressBufferedInputStream(jarFile.getInputStream(indexEntry),
 | 
				
			||||||
                    processIndexListener, new URL(repo.address), (int) indexEntry.getSize());
 | 
					                    processIndexListener, repo.address, (int) indexEntry.getSize());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Process the index...
 | 
					            // Process the index...
 | 
				
			||||||
            SAXParserFactory factory = SAXParserFactory.newInstance();
 | 
					            SAXParserFactory factory = SAXParserFactory.newInstance();
 | 
				
			||||||
@ -226,7 +225,7 @@ public class RepoUpdater {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            long timestamp = repoDetailsToSave.getAsLong(RepoTable.Cols.TIMESTAMP);
 | 
					            long timestamp = repoDetailsToSave.getAsLong(RepoTable.Cols.TIMESTAMP);
 | 
				
			||||||
            if (timestamp < repo.timestamp) {
 | 
					            if (timestamp < repo.timestamp) {
 | 
				
			||||||
                throw new UpdateException(repo, "index.jar is older that current index! "
 | 
					                throw new UpdateException("index.jar is older that current index! "
 | 
				
			||||||
                        + timestamp + " < " + repo.timestamp);
 | 
					                        + timestamp + " < " + repo.timestamp);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -237,7 +236,7 @@ public class RepoUpdater {
 | 
				
			|||||||
            assertSigningCertFromXmlCorrect();
 | 
					            assertSigningCertFromXmlCorrect();
 | 
				
			||||||
            commitToDb();
 | 
					            commitToDb();
 | 
				
			||||||
        } catch (SAXException | ParserConfigurationException | IOException e) {
 | 
					        } catch (SAXException | ParserConfigurationException | IOException e) {
 | 
				
			||||||
            throw new UpdateException(repo, "Error parsing index", e);
 | 
					            throw new UpdateException("Error parsing index", e);
 | 
				
			||||||
        } finally {
 | 
					        } finally {
 | 
				
			||||||
            FDroidApp.enableSpongyCastleOnLollipop();
 | 
					            FDroidApp.enableSpongyCastleOnLollipop();
 | 
				
			||||||
            Utils.closeQuietly(indexInputStream);
 | 
					            Utils.closeQuietly(indexInputStream);
 | 
				
			||||||
@ -251,14 +250,14 @@ public class RepoUpdater {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    protected final ProgressListener downloadListener = new ProgressListener() {
 | 
					    protected final ProgressListener downloadListener = new ProgressListener() {
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
 | 
					        public void onProgress(String urlString, long bytesRead, long totalBytes) {
 | 
				
			||||||
            UpdateService.reportDownloadProgress(context, RepoUpdater.this, bytesRead, totalBytes);
 | 
					            UpdateService.reportDownloadProgress(context, RepoUpdater.this, bytesRead, totalBytes);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected final ProgressListener processIndexListener = new ProgressListener() {
 | 
					    protected final ProgressListener processIndexListener = new ProgressListener() {
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
 | 
					        public void onProgress(String urlString, long bytesRead, long totalBytes) {
 | 
				
			||||||
            UpdateService.reportProcessIndexProgress(context, RepoUpdater.this, bytesRead, totalBytes);
 | 
					            UpdateService.reportProcessIndexProgress(context, RepoUpdater.this, bytesRead, totalBytes);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
@ -343,22 +342,19 @@ public class RepoUpdater {
 | 
				
			|||||||
    public static class UpdateException extends Exception {
 | 
					    public static class UpdateException extends Exception {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static final long serialVersionUID = -4492452418826132803L;
 | 
					        private static final long serialVersionUID = -4492452418826132803L;
 | 
				
			||||||
        public final Repo repo;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public UpdateException(Repo repo, String message) {
 | 
					        public UpdateException(String message) {
 | 
				
			||||||
            super(message);
 | 
					            super(message);
 | 
				
			||||||
            this.repo = repo;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public UpdateException(Repo repo, String message, Exception cause) {
 | 
					        public UpdateException(String message, Exception cause) {
 | 
				
			||||||
            super(message, cause);
 | 
					            super(message, cause);
 | 
				
			||||||
            this.repo = repo;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static class SigningException extends UpdateException {
 | 
					    public static class SigningException extends UpdateException {
 | 
				
			||||||
        public SigningException(Repo repo, String message) {
 | 
					        public SigningException(String message) {
 | 
				
			||||||
            super(repo, "Repository was not signed correctly: " + message);
 | 
					            super("Repository was not signed correctly: " + message);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -367,18 +363,18 @@ public class RepoUpdater {
 | 
				
			|||||||
     * signing setups that would be valid for a regular jar.  This validates those
 | 
					     * signing setups that would be valid for a regular jar.  This validates those
 | 
				
			||||||
     * restrictions.
 | 
					     * restrictions.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    X509Certificate getSigningCertFromJar(JarEntry jarEntry) throws SigningException {
 | 
					    public static X509Certificate getSigningCertFromJar(JarEntry jarEntry) throws SigningException {
 | 
				
			||||||
        final CodeSigner[] codeSigners = jarEntry.getCodeSigners();
 | 
					        final CodeSigner[] codeSigners = jarEntry.getCodeSigners();
 | 
				
			||||||
        if (codeSigners == null || codeSigners.length == 0) {
 | 
					        if (codeSigners == null || codeSigners.length == 0) {
 | 
				
			||||||
            throw new SigningException(repo, "No signature found in index");
 | 
					            throw new SigningException("No signature found in index");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        /* we could in theory support more than 1, but as of now we do not */
 | 
					        /* we could in theory support more than 1, but as of now we do not */
 | 
				
			||||||
        if (codeSigners.length > 1) {
 | 
					        if (codeSigners.length > 1) {
 | 
				
			||||||
            throw new SigningException(repo, "index.jar must be signed by a single code signer!");
 | 
					            throw new SigningException("index.jar must be signed by a single code signer!");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        List<? extends Certificate> certs = codeSigners[0].getSignerCertPath().getCertificates();
 | 
					        List<? extends Certificate> certs = codeSigners[0].getSignerCertPath().getCertificates();
 | 
				
			||||||
        if (certs.size() != 1) {
 | 
					        if (certs.size() != 1) {
 | 
				
			||||||
            throw new SigningException(repo, "index.jar code signers must only have a single certificate!");
 | 
					            throw new SigningException("index.jar code signers must only have a single certificate!");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return (X509Certificate) certs.get(0);
 | 
					        return (X509Certificate) certs.get(0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -404,7 +400,7 @@ public class RepoUpdater {
 | 
				
			|||||||
            String fingerprintFromJar = Utils.calcFingerprint(rawCertFromJar);
 | 
					            String fingerprintFromJar = Utils.calcFingerprint(rawCertFromJar);
 | 
				
			||||||
            if (!repo.fingerprint.equalsIgnoreCase(fingerprintFromIndexXml)
 | 
					            if (!repo.fingerprint.equalsIgnoreCase(fingerprintFromIndexXml)
 | 
				
			||||||
                    || !repo.fingerprint.equalsIgnoreCase(fingerprintFromJar)) {
 | 
					                    || !repo.fingerprint.equalsIgnoreCase(fingerprintFromJar)) {
 | 
				
			||||||
                throw new SigningException(repo, "Supplied certificate fingerprint does not match!");
 | 
					                throw new SigningException("Supplied certificate fingerprint does not match!");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } // else - no info to check things are valid, so just Trust On First Use
 | 
					        } // else - no info to check things are valid, so just Trust On First Use
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -435,7 +431,7 @@ public class RepoUpdater {
 | 
				
			|||||||
        if (TextUtils.isEmpty(repo.signingCertificate)
 | 
					        if (TextUtils.isEmpty(repo.signingCertificate)
 | 
				
			||||||
                || TextUtils.isEmpty(certFromJar)
 | 
					                || TextUtils.isEmpty(certFromJar)
 | 
				
			||||||
                || TextUtils.isEmpty(certFromIndexXml)) {
 | 
					                || TextUtils.isEmpty(certFromIndexXml)) {
 | 
				
			||||||
            throw new SigningException(repo, "A empty repo or signing certificate is invalid!");
 | 
					            throw new SigningException("A empty repo or signing certificate is invalid!");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // though its called repo.signingCertificate, its actually a X509 certificate
 | 
					        // though its called repo.signingCertificate, its actually a X509 certificate
 | 
				
			||||||
@ -444,7 +440,7 @@ public class RepoUpdater {
 | 
				
			|||||||
                && certFromIndexXml.equals(certFromJar)) {
 | 
					                && certFromIndexXml.equals(certFromJar)) {
 | 
				
			||||||
            return; // we have a match!
 | 
					            return; // we have a match!
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        throw new SigningException(repo, "Signing certificate does not match!");
 | 
					        throw new SigningException("Signing certificate does not match!");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
@ -516,12 +516,13 @@ public class UpdateService extends IntentService {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static void reportDownloadProgress(Context context, RepoUpdater updater, int bytesRead, int totalBytes) {
 | 
					    public static void reportDownloadProgress(Context context, RepoUpdater updater,
 | 
				
			||||||
 | 
					                                              long bytesRead, long totalBytes) {
 | 
				
			||||||
        Utils.debugLog(TAG, "Downloading " + updater.indexUrl + "(" + bytesRead + "/" + totalBytes + ")");
 | 
					        Utils.debugLog(TAG, "Downloading " + updater.indexUrl + "(" + bytesRead + "/" + totalBytes + ")");
 | 
				
			||||||
        String downloadedSizeFriendly = Utils.getFriendlySize(bytesRead);
 | 
					        String downloadedSizeFriendly = Utils.getFriendlySize(bytesRead);
 | 
				
			||||||
        int percent = -1;
 | 
					        int percent = -1;
 | 
				
			||||||
        if (totalBytes > 0) {
 | 
					        if (totalBytes > 0) {
 | 
				
			||||||
            percent = (int) ((double) bytesRead / totalBytes * 100);
 | 
					            percent = (int) (bytesRead / (totalBytes * 100L));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        String message;
 | 
					        String message;
 | 
				
			||||||
        if (totalBytes == -1) {
 | 
					        if (totalBytes == -1) {
 | 
				
			||||||
@ -534,13 +535,14 @@ public class UpdateService extends IntentService {
 | 
				
			|||||||
        sendStatus(context, STATUS_INFO, message, percent);
 | 
					        sendStatus(context, STATUS_INFO, message, percent);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static void reportProcessIndexProgress(Context context, RepoUpdater updater, int bytesRead, int totalBytes) {
 | 
					    public static void reportProcessIndexProgress(Context context, RepoUpdater updater,
 | 
				
			||||||
 | 
					                                                  long bytesRead, long totalBytes) {
 | 
				
			||||||
        Utils.debugLog(TAG, "Processing " + updater.indexUrl + "(" + bytesRead + "/" + totalBytes + ")");
 | 
					        Utils.debugLog(TAG, "Processing " + updater.indexUrl + "(" + bytesRead + "/" + totalBytes + ")");
 | 
				
			||||||
        String downloadedSize = Utils.getFriendlySize(bytesRead);
 | 
					        String downloadedSize = Utils.getFriendlySize(bytesRead);
 | 
				
			||||||
        String totalSize = Utils.getFriendlySize(totalBytes);
 | 
					        String totalSize = Utils.getFriendlySize(totalBytes);
 | 
				
			||||||
        int percent = -1;
 | 
					        int percent = -1;
 | 
				
			||||||
        if (totalBytes > 0) {
 | 
					        if (totalBytes > 0) {
 | 
				
			||||||
            percent = (int) ((double) bytesRead / totalBytes * 100);
 | 
					            percent = (int) (bytesRead / (totalBytes * 100L));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        String message = context.getString(R.string.status_processing_xml_percent, updater.indexUrl, downloadedSize, totalSize, percent);
 | 
					        String message = context.getString(R.string.status_processing_xml_percent, updater.indexUrl, downloadedSize, totalSize, percent);
 | 
				
			||||||
        sendStatus(context, STATUS_INFO, message, percent);
 | 
					        sendStatus(context, STATUS_INFO, message, percent);
 | 
				
			||||||
 | 
				
			|||||||
@ -88,35 +88,36 @@ public class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
            + RepoTable.Cols.TIMESTAMP + " integer not null default 0, "
 | 
					            + RepoTable.Cols.TIMESTAMP + " integer not null default 0, "
 | 
				
			||||||
            + RepoTable.Cols.ICON + " string, "
 | 
					            + RepoTable.Cols.ICON + " string, "
 | 
				
			||||||
            + RepoTable.Cols.MIRRORS + " string, "
 | 
					            + RepoTable.Cols.MIRRORS + " string, "
 | 
				
			||||||
 | 
					            + RepoTable.Cols.USER_MIRRORS + " string, "
 | 
				
			||||||
            + RepoTable.Cols.PUSH_REQUESTS + " integer not null default " + Repo.PUSH_REQUEST_IGNORE
 | 
					            + RepoTable.Cols.PUSH_REQUESTS + " integer not null default " + Repo.PUSH_REQUEST_IGNORE
 | 
				
			||||||
            + ");";
 | 
					            + ");";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static final String CREATE_TABLE_APK =
 | 
					    static final String CREATE_TABLE_APK =
 | 
				
			||||||
            "CREATE TABLE " + ApkTable.NAME + " ( "
 | 
					            "CREATE TABLE " + ApkTable.NAME + " ( "
 | 
				
			||||||
            + ApkTable.Cols.APP_ID + " integer not null, "
 | 
					                    + ApkTable.Cols.APP_ID + " integer not null, "
 | 
				
			||||||
            + ApkTable.Cols.VERSION_NAME + " text not null, "
 | 
					                    + ApkTable.Cols.VERSION_NAME + " text not null, "
 | 
				
			||||||
            + ApkTable.Cols.REPO_ID + " integer not null, "
 | 
					                    + ApkTable.Cols.REPO_ID + " integer not null, "
 | 
				
			||||||
            + ApkTable.Cols.HASH + " text not null, "
 | 
					                    + ApkTable.Cols.HASH + " text not null, "
 | 
				
			||||||
            + ApkTable.Cols.VERSION_CODE + " int not null,"
 | 
					                    + ApkTable.Cols.VERSION_CODE + " int not null,"
 | 
				
			||||||
            + ApkTable.Cols.NAME + " text not null, "
 | 
					                    + ApkTable.Cols.NAME + " text not null, "
 | 
				
			||||||
            + ApkTable.Cols.SIZE + " int not null, "
 | 
					                    + ApkTable.Cols.SIZE + " int not null, "
 | 
				
			||||||
            + ApkTable.Cols.SIGNATURE + " string, "
 | 
					                    + ApkTable.Cols.SIGNATURE + " string, "
 | 
				
			||||||
            + ApkTable.Cols.SOURCE_NAME + " string, "
 | 
					                    + ApkTable.Cols.SOURCE_NAME + " string, "
 | 
				
			||||||
            + ApkTable.Cols.MIN_SDK_VERSION + " integer, "
 | 
					                    + ApkTable.Cols.MIN_SDK_VERSION + " integer, "
 | 
				
			||||||
            + ApkTable.Cols.TARGET_SDK_VERSION + " integer, "
 | 
					                    + ApkTable.Cols.TARGET_SDK_VERSION + " integer, "
 | 
				
			||||||
            + ApkTable.Cols.MAX_SDK_VERSION + " integer, "
 | 
					                    + ApkTable.Cols.MAX_SDK_VERSION + " integer, "
 | 
				
			||||||
            + ApkTable.Cols.OBB_MAIN_FILE + " string, "
 | 
					                    + ApkTable.Cols.OBB_MAIN_FILE + " string, "
 | 
				
			||||||
            + ApkTable.Cols.OBB_MAIN_FILE_SHA256 + " string, "
 | 
					                    + ApkTable.Cols.OBB_MAIN_FILE_SHA256 + " string, "
 | 
				
			||||||
            + ApkTable.Cols.OBB_PATCH_FILE + " string, "
 | 
					                    + ApkTable.Cols.OBB_PATCH_FILE + " string, "
 | 
				
			||||||
            + ApkTable.Cols.OBB_PATCH_FILE_SHA256 + " string, "
 | 
					                    + ApkTable.Cols.OBB_PATCH_FILE_SHA256 + " string, "
 | 
				
			||||||
            + ApkTable.Cols.REQUESTED_PERMISSIONS + " string, "
 | 
					                    + ApkTable.Cols.REQUESTED_PERMISSIONS + " string, "
 | 
				
			||||||
            + ApkTable.Cols.FEATURES + " string, "
 | 
					                    + ApkTable.Cols.FEATURES + " string, "
 | 
				
			||||||
            + ApkTable.Cols.NATIVE_CODE + " string, "
 | 
					                    + ApkTable.Cols.NATIVE_CODE + " string, "
 | 
				
			||||||
            + ApkTable.Cols.HASH_TYPE + " string, "
 | 
					                    + ApkTable.Cols.HASH_TYPE + " string, "
 | 
				
			||||||
            + ApkTable.Cols.ADDED_DATE + " string, "
 | 
					                    + ApkTable.Cols.ADDED_DATE + " string, "
 | 
				
			||||||
            + ApkTable.Cols.IS_COMPATIBLE + " int not null, "
 | 
					                    + ApkTable.Cols.IS_COMPATIBLE + " int not null, "
 | 
				
			||||||
            + ApkTable.Cols.INCOMPATIBLE_REASONS + " text"
 | 
					                    + ApkTable.Cols.INCOMPATIBLE_REASONS + " text"
 | 
				
			||||||
            + ");";
 | 
					                    + ");";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static final String CREATE_TABLE_APP_METADATA = "CREATE TABLE " + AppMetadataTable.NAME
 | 
					    static final String CREATE_TABLE_APP_METADATA = "CREATE TABLE " + AppMetadataTable.NAME
 | 
				
			||||||
            + " ( "
 | 
					            + " ( "
 | 
				
			||||||
@ -181,7 +182,7 @@ public class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
     * app metadata id, because it can instead look through the primary key index. This can be
 | 
					     * app metadata id, because it can instead look through the primary key index. This can be
 | 
				
			||||||
     * observed by flipping the order of the primary key columns, and noting the resulting sqlite
 | 
					     * observed by flipping the order of the primary key columns, and noting the resulting sqlite
 | 
				
			||||||
     * logs along the lines of:
 | 
					     * logs along the lines of:
 | 
				
			||||||
     *   E/SQLiteLog(14164): (284) automatic index on fdroid_categoryAppMetadataJoin(appMetadataId)
 | 
					     * E/SQLiteLog(14164): (284) automatic index on fdroid_categoryAppMetadataJoin(appMetadataId)
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static final String CREATE_TABLE_CAT_JOIN = "CREATE TABLE " + CatJoinTable.NAME
 | 
					    static final String CREATE_TABLE_CAT_JOIN = "CREATE TABLE " + CatJoinTable.NAME
 | 
				
			||||||
            + " ( "
 | 
					            + " ( "
 | 
				
			||||||
@ -214,7 +215,7 @@ public class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
            + "primary key(" + ApkAntiFeatureJoinTable.Cols.APK_ID + ", " + ApkAntiFeatureJoinTable.Cols.ANTI_FEATURE_ID + ") "
 | 
					            + "primary key(" + ApkAntiFeatureJoinTable.Cols.APK_ID + ", " + ApkAntiFeatureJoinTable.Cols.ANTI_FEATURE_ID + ") "
 | 
				
			||||||
            + " );";
 | 
					            + " );";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected static final int DB_VERSION = 77;
 | 
					    protected static final int DB_VERSION = 78;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final Context context;
 | 
					    private final Context context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -321,6 +322,17 @@ public class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
        addApkAntiFeatures(db, oldVersion);
 | 
					        addApkAntiFeatures(db, oldVersion);
 | 
				
			||||||
        addIgnoreVulnPref(db, oldVersion);
 | 
					        addIgnoreVulnPref(db, oldVersion);
 | 
				
			||||||
        addLiberapayID(db, oldVersion);
 | 
					        addLiberapayID(db, oldVersion);
 | 
				
			||||||
 | 
					        addUserMirrorsFields(db, oldVersion);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void addUserMirrorsFields(SQLiteDatabase db, int oldVersion) {
 | 
				
			||||||
 | 
					        if (oldVersion >= 78) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!columnExists(db, RepoTable.NAME, RepoTable.Cols.USER_MIRRORS)) {
 | 
				
			||||||
 | 
					            Utils.debugLog(TAG, "Adding " + RepoTable.Cols.USER_MIRRORS + " field to " + RepoTable.NAME + " table in db.");
 | 
				
			||||||
 | 
					            db.execSQL("alter table " + RepoTable.NAME + " add column " + RepoTable.Cols.USER_MIRRORS + " string;");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void addLiberapayID(SQLiteDatabase db, int oldVersion) {
 | 
					    private void addLiberapayID(SQLiteDatabase db, int oldVersion) {
 | 
				
			||||||
@ -581,7 +593,7 @@ public class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
        updateRepoPriority(db, gpPubKey, gpArchiveAddress, 4);
 | 
					        updateRepoPriority(db, gpPubKey, gpArchiveAddress, 4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        int priority = 5;
 | 
					        int priority = 5;
 | 
				
			||||||
        String[] projection = new String[] {RepoTable.Cols.SIGNING_CERT, RepoTable.Cols.ADDRESS};
 | 
					        String[] projection = new String[]{RepoTable.Cols.SIGNING_CERT, RepoTable.Cols.ADDRESS};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Order by ID, because that is a good analogy for the order in which they were added.
 | 
					        // Order by ID, because that is a good analogy for the order in which they were added.
 | 
				
			||||||
        // The order in which they were added is likely the order they present in the ManageRepos activity.
 | 
					        // The order in which they were added is likely the order they present in the ManageRepos activity.
 | 
				
			||||||
@ -606,7 +618,7 @@ public class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
                RepoTable.NAME,
 | 
					                RepoTable.NAME,
 | 
				
			||||||
                values,
 | 
					                values,
 | 
				
			||||||
                RepoTable.Cols.SIGNING_CERT + " = ? AND " + RepoTable.Cols.ADDRESS + " = ?",
 | 
					                RepoTable.Cols.SIGNING_CERT + " = ? AND " + RepoTable.Cols.ADDRESS + " = ?",
 | 
				
			||||||
                new String[] {signingCert, address}
 | 
					                new String[]{signingCert, address}
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -630,15 +642,15 @@ public class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
        Utils.debugLog(TAG, "Migrating app preferences to separate table");
 | 
					        Utils.debugLog(TAG, "Migrating app preferences to separate table");
 | 
				
			||||||
        db.execSQL(
 | 
					        db.execSQL(
 | 
				
			||||||
                "INSERT INTO " + AppPrefsTable.NAME + " ("
 | 
					                "INSERT INTO " + AppPrefsTable.NAME + " ("
 | 
				
			||||||
                + AppPrefsTable.Cols.PACKAGE_NAME + ", "
 | 
					                        + AppPrefsTable.Cols.PACKAGE_NAME + ", "
 | 
				
			||||||
                + AppPrefsTable.Cols.IGNORE_THIS_UPDATE + ", "
 | 
					                        + AppPrefsTable.Cols.IGNORE_THIS_UPDATE + ", "
 | 
				
			||||||
                + AppPrefsTable.Cols.IGNORE_ALL_UPDATES
 | 
					                        + AppPrefsTable.Cols.IGNORE_ALL_UPDATES
 | 
				
			||||||
                + ") SELECT "
 | 
					                        + ") SELECT "
 | 
				
			||||||
                + "id, "
 | 
					                        + "id, "
 | 
				
			||||||
                + "ignoreThisUpdate, "
 | 
					                        + "ignoreThisUpdate, "
 | 
				
			||||||
                + "ignoreAllUpdates "
 | 
					                        + "ignoreAllUpdates "
 | 
				
			||||||
                + "FROM " + AppMetadataTable.NAME + " "
 | 
					                        + "FROM " + AppMetadataTable.NAME + " "
 | 
				
			||||||
                + "WHERE ignoreThisUpdate > 0 OR ignoreAllUpdates > 0"
 | 
					                        + "WHERE ignoreThisUpdate > 0 OR ignoreAllUpdates > 0"
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        resetTransient(db);
 | 
					        resetTransient(db);
 | 
				
			||||||
@ -687,7 +699,7 @@ public class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                db.execSQL(createTableDdl);
 | 
					                db.execSQL(createTableDdl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                String nonPackageNameFields = TextUtils.join(", ", new String[] {
 | 
					                String nonPackageNameFields = TextUtils.join(", ", new String[]{
 | 
				
			||||||
                        ApkTable.Cols.APP_ID,
 | 
					                        ApkTable.Cols.APP_ID,
 | 
				
			||||||
                        ApkTable.Cols.VERSION_NAME,
 | 
					                        ApkTable.Cols.VERSION_NAME,
 | 
				
			||||||
                        ApkTable.Cols.REPO_ID,
 | 
					                        ApkTable.Cols.REPO_ID,
 | 
				
			||||||
@ -766,7 +778,7 @@ public class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        List<Repo> oldrepos = new ArrayList<>();
 | 
					        List<Repo> oldrepos = new ArrayList<>();
 | 
				
			||||||
        Cursor cursor = db.query(RepoTable.NAME,
 | 
					        Cursor cursor = db.query(RepoTable.NAME,
 | 
				
			||||||
                new String[] {RepoTable.Cols.ADDRESS, RepoTable.Cols.IN_USE, RepoTable.Cols.SIGNING_CERT},
 | 
					                new String[]{RepoTable.Cols.ADDRESS, RepoTable.Cols.IN_USE, RepoTable.Cols.SIGNING_CERT},
 | 
				
			||||||
                null, null, null, null, null);
 | 
					                null, null, null, null, null);
 | 
				
			||||||
        if (cursor != null) {
 | 
					        if (cursor != null) {
 | 
				
			||||||
            if (cursor.getCount() > 0) {
 | 
					            if (cursor.getCount() > 0) {
 | 
				
			||||||
@ -847,7 +859,7 @@ public class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        List<Repo> oldrepos = new ArrayList<>();
 | 
					        List<Repo> oldrepos = new ArrayList<>();
 | 
				
			||||||
        Cursor cursor = db.query(RepoTable.NAME,
 | 
					        Cursor cursor = db.query(RepoTable.NAME,
 | 
				
			||||||
                new String[] {RepoTable.Cols.ADDRESS, RepoTable.Cols.SIGNING_CERT},
 | 
					                new String[]{RepoTable.Cols.ADDRESS, RepoTable.Cols.SIGNING_CERT},
 | 
				
			||||||
                null, null, null, null, null);
 | 
					                null, null, null, null, null);
 | 
				
			||||||
        if (cursor != null) {
 | 
					        if (cursor != null) {
 | 
				
			||||||
            if (cursor.getCount() > 0) {
 | 
					            if (cursor.getCount() > 0) {
 | 
				
			||||||
@ -865,7 +877,7 @@ public class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
        for (final Repo repo : oldrepos) {
 | 
					        for (final Repo repo : oldrepos) {
 | 
				
			||||||
            ContentValues values = new ContentValues();
 | 
					            ContentValues values = new ContentValues();
 | 
				
			||||||
            values.put(RepoTable.Cols.FINGERPRINT, Utils.calcFingerprint(repo.signingCertificate));
 | 
					            values.put(RepoTable.Cols.FINGERPRINT, Utils.calcFingerprint(repo.signingCertificate));
 | 
				
			||||||
            db.update(RepoTable.NAME, values, RepoTable.Cols.ADDRESS + " = ?", new String[] {repo.address});
 | 
					            db.update(RepoTable.NAME, values, RepoTable.Cols.ADDRESS + " = ?", new String[]{repo.address});
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -954,7 +966,7 @@ public class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            db.execSQL(createTableDdl);
 | 
					            db.execSQL(createTableDdl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            String nonIdFields = TextUtils.join(", ", new String[] {
 | 
					            String nonIdFields = TextUtils.join(", ", new String[]{
 | 
				
			||||||
                    RepoTable.Cols.ADDRESS,
 | 
					                    RepoTable.Cols.ADDRESS,
 | 
				
			||||||
                    RepoTable.Cols.NAME,
 | 
					                    RepoTable.Cols.NAME,
 | 
				
			||||||
                    RepoTable.Cols.DESCRIPTION,
 | 
					                    RepoTable.Cols.DESCRIPTION,
 | 
				
			||||||
@ -1235,8 +1247,8 @@ public class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static boolean tableExists(SQLiteDatabase db, String table) {
 | 
					    private static boolean tableExists(SQLiteDatabase db, String table) {
 | 
				
			||||||
        Cursor cursor = db.query("sqlite_master", new String[] {"name"},
 | 
					        Cursor cursor = db.query("sqlite_master", new String[]{"name"},
 | 
				
			||||||
                "type = 'table' AND name = ?", new String[] {table}, null, null, null);
 | 
					                "type = 'table' AND name = ?", new String[]{table}, null, null, null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        boolean exists = cursor.getCount() > 0;
 | 
					        boolean exists = cursor.getCount() > 0;
 | 
				
			||||||
        cursor.close();
 | 
					        cursor.close();
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,6 @@ import android.content.Context;
 | 
				
			|||||||
import android.content.Intent;
 | 
					import android.content.Intent;
 | 
				
			||||||
import android.net.Uri;
 | 
					import android.net.Uri;
 | 
				
			||||||
import android.text.TextUtils;
 | 
					import android.text.TextUtils;
 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.fdroid.fdroid.R;
 | 
					import org.fdroid.fdroid.R;
 | 
				
			||||||
import org.fdroid.fdroid.Utils;
 | 
					import org.fdroid.fdroid.Utils;
 | 
				
			||||||
import org.fdroid.fdroid.localrepo.peers.WifiPeer;
 | 
					import org.fdroid.fdroid.localrepo.peers.WifiPeer;
 | 
				
			||||||
@ -82,17 +81,12 @@ public class NewRepoConfig {
 | 
				
			|||||||
        scheme = scheme.toLowerCase(Locale.ENGLISH);
 | 
					        scheme = scheme.toLowerCase(Locale.ENGLISH);
 | 
				
			||||||
        host = host.toLowerCase(Locale.ENGLISH);
 | 
					        host = host.toLowerCase(Locale.ENGLISH);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // We only listen for /fdroid/archive or /fdroid/repo paths when receiving a HTTP(S) intent.
 | 
					        if (uri.getPath() == null
 | 
				
			||||||
        // For fdroidrepo(s) intents, we are less picky and will accept any path.
 | 
					                || !Arrays.asList("https", "http", "fdroidrepos", "fdroidrepo").contains(scheme)) {
 | 
				
			||||||
        boolean isHttpScheme = TextUtils.equals("http", scheme) || TextUtils.equals("https", scheme);
 | 
					 | 
				
			||||||
        String path = uri.getPath();
 | 
					 | 
				
			||||||
        if (path == null || isHttpScheme && !(path.contains("/fdroid/archive") || path.contains("/fdroid/repo"))) {
 | 
					 | 
				
			||||||
            isValidRepo = false;
 | 
					            isValidRepo = false;
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        boolean isFdroidScheme = TextUtils.equals("fdroidrepo", scheme) || TextUtils.equals("fdroidrepos", scheme);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        String userInfo = uri.getUserInfo();
 | 
					        String userInfo = uri.getUserInfo();
 | 
				
			||||||
        if (userInfo != null) {
 | 
					        if (userInfo != null) {
 | 
				
			||||||
            String[] userInfoTokens = userInfo.split(":");
 | 
					            String[] userInfoTokens = userInfo.split(":");
 | 
				
			||||||
@ -109,15 +103,8 @@ public class NewRepoConfig {
 | 
				
			|||||||
        bssid = uri.getQueryParameter("bssid");
 | 
					        bssid = uri.getQueryParameter("bssid");
 | 
				
			||||||
        ssid = uri.getQueryParameter("ssid");
 | 
					        ssid = uri.getQueryParameter("ssid");
 | 
				
			||||||
        fromSwap = uri.getQueryParameter("swap") != null;
 | 
					        fromSwap = uri.getQueryParameter("swap") != null;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!isFdroidScheme && !isHttpScheme) {
 | 
					 | 
				
			||||||
            isValidRepo = false;
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        uriString = sanitizeRepoUri(uri);
 | 
					        uriString = sanitizeRepoUri(uri);
 | 
				
			||||||
        isValidRepo = true;
 | 
					        isValidRepo = true;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public String getBssid() {
 | 
					    public String getBssid() {
 | 
				
			||||||
@ -175,7 +162,9 @@ public class NewRepoConfig {
 | 
				
			|||||||
        return errorMessage;
 | 
					        return errorMessage;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** Sanitize and format an incoming repo URI for function and readability */
 | 
					    /**
 | 
				
			||||||
 | 
					     * Sanitize and format an incoming repo URI for function and readability
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    public static String sanitizeRepoUri(Uri uri) {
 | 
					    public static String sanitizeRepoUri(Uri uri) {
 | 
				
			||||||
        String scheme = uri.getScheme();
 | 
					        String scheme = uri.getScheme();
 | 
				
			||||||
        String host = uri.getHost();
 | 
					        String host = uri.getHost();
 | 
				
			||||||
 | 
				
			|||||||
@ -26,13 +26,13 @@ package org.fdroid.fdroid.data;
 | 
				
			|||||||
import android.content.ContentValues;
 | 
					import android.content.ContentValues;
 | 
				
			||||||
import android.database.Cursor;
 | 
					import android.database.Cursor;
 | 
				
			||||||
import android.text.TextUtils;
 | 
					import android.text.TextUtils;
 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.fdroid.fdroid.FDroidApp;
 | 
					import org.fdroid.fdroid.FDroidApp;
 | 
				
			||||||
import org.fdroid.fdroid.Utils;
 | 
					import org.fdroid.fdroid.Utils;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.RepoTable.Cols;
 | 
					import org.fdroid.fdroid.data.Schema.RepoTable.Cols;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.net.MalformedURLException;
 | 
					import java.net.MalformedURLException;
 | 
				
			||||||
import java.net.URL;
 | 
					import java.net.URL;
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
import java.util.Arrays;
 | 
					import java.util.Arrays;
 | 
				
			||||||
import java.util.Collections;
 | 
					import java.util.Collections;
 | 
				
			||||||
import java.util.Date;
 | 
					import java.util.Date;
 | 
				
			||||||
@ -94,6 +94,11 @@ public class Repo extends ValueObject {
 | 
				
			|||||||
    /** Official mirrors of this repo, considered automatically interchangeable */
 | 
					    /** Official mirrors of this repo, considered automatically interchangeable */
 | 
				
			||||||
    public String[] mirrors;
 | 
					    public String[] mirrors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Mirrors added by the user, either by UI input or by attaching removeable storage
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public String[] userMirrors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** How to treat push requests included in this repo's index XML */
 | 
					    /** How to treat push requests included in this repo's index XML */
 | 
				
			||||||
    public int pushRequests = PUSH_REQUEST_IGNORE;
 | 
					    public int pushRequests = PUSH_REQUEST_IGNORE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -160,6 +165,9 @@ public class Repo extends ValueObject {
 | 
				
			|||||||
                case Cols.MIRRORS:
 | 
					                case Cols.MIRRORS:
 | 
				
			||||||
                    mirrors = Utils.parseCommaSeparatedString(cursor.getString(i));
 | 
					                    mirrors = Utils.parseCommaSeparatedString(cursor.getString(i));
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
 | 
					                case Cols.USER_MIRRORS:
 | 
				
			||||||
 | 
					                    userMirrors = Utils.parseCommaSeparatedString(cursor.getString(i));
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
                case Cols.PUSH_REQUESTS:
 | 
					                case Cols.PUSH_REQUESTS:
 | 
				
			||||||
                    pushRequests = cursor.getInt(i);
 | 
					                    pushRequests = cursor.getInt(i);
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
@ -297,26 +305,43 @@ public class Repo extends ValueObject {
 | 
				
			|||||||
            mirrors = Utils.parseCommaSeparatedString(values.getAsString(Cols.MIRRORS));
 | 
					            mirrors = Utils.parseCommaSeparatedString(values.getAsString(Cols.MIRRORS));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (values.containsKey(Cols.USER_MIRRORS)) {
 | 
				
			||||||
 | 
					            userMirrors = Utils.parseCommaSeparatedString(values.getAsString(Cols.USER_MIRRORS));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (values.containsKey(Cols.PUSH_REQUESTS)) {
 | 
					        if (values.containsKey(Cols.PUSH_REQUESTS)) {
 | 
				
			||||||
            pushRequests = toInt(values.getAsInteger(Cols.PUSH_REQUESTS));
 | 
					            pushRequests = toInt(values.getAsInteger(Cols.PUSH_REQUESTS));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public boolean hasMirrors() {
 | 
					    public boolean hasMirrors() {
 | 
				
			||||||
        return mirrors != null && mirrors.length > 1;
 | 
					        return (mirrors != null && mirrors.length > 1)
 | 
				
			||||||
 | 
					                || (userMirrors != null && userMirrors.length > 0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public List<String> getMirrorList() {
 | 
				
			||||||
 | 
					        final ArrayList<String> allMirrors = new ArrayList<String>();
 | 
				
			||||||
 | 
					        if (userMirrors != null) {
 | 
				
			||||||
 | 
					            allMirrors.addAll(Arrays.asList(userMirrors));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (mirrors != null) {
 | 
				
			||||||
 | 
					            allMirrors.addAll(Arrays.asList(mirrors));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return allMirrors;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the number of available mirrors, including the canonical repo.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    public int getMirrorCount() {
 | 
					    public int getMirrorCount() {
 | 
				
			||||||
        int count = 0;
 | 
					        int count = 0;
 | 
				
			||||||
        if (mirrors != null && mirrors.length > 1) {
 | 
					        for (String m : getMirrorList()) {
 | 
				
			||||||
            for (String m: mirrors) {
 | 
					            if (!m.equals(address)) {
 | 
				
			||||||
                if (!m.equals(address)) {
 | 
					                if (FDroidApp.isUsingTor()) {
 | 
				
			||||||
                    if (FDroidApp.isUsingTor()) {
 | 
					                    count++;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    if (!m.contains(".onion")) {
 | 
				
			||||||
                        count++;
 | 
					                        count++;
 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        if (!m.contains(".onion")) {
 | 
					 | 
				
			||||||
                            count++;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -328,7 +353,7 @@ public class Repo extends ValueObject {
 | 
				
			|||||||
        if (TextUtils.isEmpty(lastWorkingMirror)) {
 | 
					        if (TextUtils.isEmpty(lastWorkingMirror)) {
 | 
				
			||||||
            lastWorkingMirror = address;
 | 
					            lastWorkingMirror = address;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        List<String> shuffledMirrors = Arrays.asList(mirrors);
 | 
					        List<String> shuffledMirrors = getMirrorList();
 | 
				
			||||||
        Collections.shuffle(shuffledMirrors);
 | 
					        Collections.shuffle(shuffledMirrors);
 | 
				
			||||||
        if (shuffledMirrors.size() > 1) {
 | 
					        if (shuffledMirrors.size() > 1) {
 | 
				
			||||||
            for (String m : shuffledMirrors) {
 | 
					            for (String m : shuffledMirrors) {
 | 
				
			||||||
 | 
				
			|||||||
@ -106,7 +106,7 @@ public class RepoPersister {
 | 
				
			|||||||
        try {
 | 
					        try {
 | 
				
			||||||
            context.getContentResolver().applyBatch(TempApkProvider.getAuthority(), apkOperations);
 | 
					            context.getContentResolver().applyBatch(TempApkProvider.getAuthority(), apkOperations);
 | 
				
			||||||
        } catch (RemoteException | OperationApplicationException e) {
 | 
					        } catch (RemoteException | OperationApplicationException e) {
 | 
				
			||||||
            throw new RepoUpdater.UpdateException(repo, "An internal error occurred while updating the database", e);
 | 
					            throw new RepoUpdater.UpdateException("An internal error occurred while updating the database", e);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -122,7 +122,7 @@ public class RepoPersister {
 | 
				
			|||||||
            context.getContentResolver().applyBatch(TempAppProvider.getAuthority(), appOperations);
 | 
					            context.getContentResolver().applyBatch(TempAppProvider.getAuthority(), appOperations);
 | 
				
			||||||
            return getIdsForPackages(appsToSave);
 | 
					            return getIdsForPackages(appsToSave);
 | 
				
			||||||
        } catch (RemoteException | OperationApplicationException e) {
 | 
					        } catch (RemoteException | OperationApplicationException e) {
 | 
				
			||||||
            throw new RepoUpdater.UpdateException(repo, "An internal error occurred while updating the database", e);
 | 
					            throw new RepoUpdater.UpdateException("An internal error occurred while updating the database", e);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -362,12 +362,13 @@ public interface Schema {
 | 
				
			|||||||
            String TIMESTAMP    = "timestamp";
 | 
					            String TIMESTAMP    = "timestamp";
 | 
				
			||||||
            String ICON         = "icon";
 | 
					            String ICON         = "icon";
 | 
				
			||||||
            String MIRRORS      = "mirrors";
 | 
					            String MIRRORS      = "mirrors";
 | 
				
			||||||
 | 
					            String USER_MIRRORS = "userMirrors";
 | 
				
			||||||
            String PUSH_REQUESTS = "pushRequests";
 | 
					            String PUSH_REQUESTS = "pushRequests";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            String[] ALL = {
 | 
					            String[] ALL = {
 | 
				
			||||||
                    _ID, ADDRESS, NAME, DESCRIPTION, IN_USE, PRIORITY, SIGNING_CERT,
 | 
					                    _ID, ADDRESS, NAME, DESCRIPTION, IN_USE, PRIORITY, SIGNING_CERT,
 | 
				
			||||||
                    FINGERPRINT, MAX_AGE, LAST_UPDATED, LAST_ETAG, VERSION, IS_SWAP,
 | 
					                    FINGERPRINT, MAX_AGE, LAST_UPDATED, LAST_ETAG, VERSION, IS_SWAP,
 | 
				
			||||||
                    USERNAME, PASSWORD, TIMESTAMP, ICON, MIRRORS, PUSH_REQUESTS,
 | 
					                    USERNAME, PASSWORD, TIMESTAMP, ICON, MIRRORS, USER_MIRRORS, PUSH_REQUESTS,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,8 @@
 | 
				
			|||||||
package org.fdroid.fdroid.net;
 | 
					package org.fdroid.fdroid.net;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.net.Uri;
 | 
				
			||||||
import android.support.annotation.Nullable;
 | 
					import android.support.annotation.Nullable;
 | 
				
			||||||
import android.util.Log;
 | 
					import android.util.Log;
 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.apache.commons.io.input.BoundedInputStream;
 | 
					import org.apache.commons.io.input.BoundedInputStream;
 | 
				
			||||||
import org.fdroid.fdroid.Utils;
 | 
					import org.fdroid.fdroid.Utils;
 | 
				
			||||||
import org.fdroid.fdroid.net.bluetooth.BluetoothClient;
 | 
					import org.fdroid.fdroid.net.bluetooth.BluetoothClient;
 | 
				
			||||||
@ -14,7 +14,6 @@ import org.fdroid.fdroid.net.bluetooth.httpish.Response;
 | 
				
			|||||||
import java.io.File;
 | 
					import java.io.File;
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.io.InputStream;
 | 
					import java.io.InputStream;
 | 
				
			||||||
import java.net.URL;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class BluetoothDownloader extends Downloader {
 | 
					public class BluetoothDownloader extends Downloader {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -24,10 +23,11 @@ public class BluetoothDownloader extends Downloader {
 | 
				
			|||||||
    private FileDetails fileDetails;
 | 
					    private FileDetails fileDetails;
 | 
				
			||||||
    private final String sourcePath;
 | 
					    private final String sourcePath;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public BluetoothDownloader(String macAddress, URL sourceUrl, File destFile) throws IOException {
 | 
					    public BluetoothDownloader(Uri uri, File destFile) throws IOException {
 | 
				
			||||||
        super(sourceUrl, destFile);
 | 
					        super(uri, destFile);
 | 
				
			||||||
 | 
					        String macAddress = uri.getHost().replace("-", ":");
 | 
				
			||||||
        this.connection = new BluetoothClient(macAddress).openConnection();
 | 
					        this.connection = new BluetoothClient(macAddress).openConnection();
 | 
				
			||||||
        this.sourcePath = sourceUrl.getPath();
 | 
					        this.sourcePath = uri.getPath();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
@ -58,7 +58,7 @@ public class BluetoothDownloader extends Downloader {
 | 
				
			|||||||
        if (fileDetails == null) {
 | 
					        if (fileDetails == null) {
 | 
				
			||||||
            Utils.debugLog(TAG, "Going to Bluetooth \"server\" to get file details.");
 | 
					            Utils.debugLog(TAG, "Going to Bluetooth \"server\" to get file details.");
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                fileDetails = Request.createHEAD(sourceUrl.getPath(), connection).send().toFileDetails();
 | 
					                fileDetails = Request.createHEAD(sourcePath, connection).send().toFileDetails();
 | 
				
			||||||
            } catch (IOException e) {
 | 
					            } catch (IOException e) {
 | 
				
			||||||
                Log.e(TAG, "Error getting file details from Bluetooth \"server\"", e);
 | 
					                Log.e(TAG, "Error getting file details from Bluetooth \"server\"", e);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -73,7 +73,7 @@ public class BluetoothDownloader extends Downloader {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public int totalDownloadSize() {
 | 
					    public long totalDownloadSize() {
 | 
				
			||||||
        FileDetails details = getFileDetails();
 | 
					        FileDetails details = getFileDetails();
 | 
				
			||||||
        return details != null ? details.getFileSize() : -1;
 | 
					        return details != null ? details.getFileSize() : -1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
package org.fdroid.fdroid.net;
 | 
					package org.fdroid.fdroid.net;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.net.Uri;
 | 
				
			||||||
import org.fdroid.fdroid.ProgressListener;
 | 
					import org.fdroid.fdroid.ProgressListener;
 | 
				
			||||||
import org.fdroid.fdroid.Utils;
 | 
					import org.fdroid.fdroid.Utils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -9,7 +10,6 @@ import java.io.IOException;
 | 
				
			|||||||
import java.io.InputStream;
 | 
					import java.io.InputStream;
 | 
				
			||||||
import java.io.OutputStream;
 | 
					import java.io.OutputStream;
 | 
				
			||||||
import java.net.ConnectException;
 | 
					import java.net.ConnectException;
 | 
				
			||||||
import java.net.URL;
 | 
					 | 
				
			||||||
import java.util.Timer;
 | 
					import java.util.Timer;
 | 
				
			||||||
import java.util.TimerTask;
 | 
					import java.util.TimerTask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -32,12 +32,12 @@ public abstract class Downloader {
 | 
				
			|||||||
    public static final String EXTRA_MIRROR_URL = "org.fdroid.fdroid.net.Downloader.extra.ERROR_MIRROR_URL";
 | 
					    public static final String EXTRA_MIRROR_URL = "org.fdroid.fdroid.net.Downloader.extra.ERROR_MIRROR_URL";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private volatile boolean cancelled = false;
 | 
					    private volatile boolean cancelled = false;
 | 
				
			||||||
    private volatile int bytesRead;
 | 
					    private volatile long bytesRead;
 | 
				
			||||||
    private volatile int totalBytes;
 | 
					    private volatile long totalBytes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public final File outputFile;
 | 
					    public final File outputFile;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final URL sourceUrl;
 | 
					    final String urlString;
 | 
				
			||||||
    String cacheTag;
 | 
					    String cacheTag;
 | 
				
			||||||
    boolean notFound;
 | 
					    boolean notFound;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -52,8 +52,8 @@ public abstract class Downloader {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    protected abstract void close();
 | 
					    protected abstract void close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Downloader(URL url, File destFile) {
 | 
					    Downloader(Uri uri, File destFile) {
 | 
				
			||||||
        this.sourceUrl = url;
 | 
					        this.urlString = uri.toString();
 | 
				
			||||||
        outputFile = destFile;
 | 
					        outputFile = destFile;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -92,7 +92,7 @@ public abstract class Downloader {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public abstract boolean hasChanged();
 | 
					    public abstract boolean hasChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected abstract int totalDownloadSize();
 | 
					    protected abstract long totalDownloadSize();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public abstract void download() throws ConnectException, IOException, InterruptedException;
 | 
					    public abstract void download() throws ConnectException, IOException, InterruptedException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -201,7 +201,7 @@ public abstract class Downloader {
 | 
				
			|||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        public void run() {
 | 
					        public void run() {
 | 
				
			||||||
            if (downloaderProgressListener != null) {
 | 
					            if (downloaderProgressListener != null) {
 | 
				
			||||||
                downloaderProgressListener.onProgress(sourceUrl, bytesRead, totalBytes);
 | 
					                downloaderProgressListener.onProgress(urlString, bytesRead, totalBytes);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
				
			|||||||
@ -2,19 +2,15 @@ package org.fdroid.fdroid.net;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.content.Context;
 | 
					import android.content.Context;
 | 
				
			||||||
import android.net.Uri;
 | 
					import android.net.Uri;
 | 
				
			||||||
import android.support.v4.content.LocalBroadcastManager;
 | 
					 | 
				
			||||||
import org.fdroid.fdroid.data.Repo;
 | 
					import org.fdroid.fdroid.data.Repo;
 | 
				
			||||||
import org.fdroid.fdroid.data.RepoProvider;
 | 
					import org.fdroid.fdroid.data.RepoProvider;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema;
 | 
					import org.fdroid.fdroid.data.Schema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.File;
 | 
					import java.io.File;
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.net.URL;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class DownloaderFactory {
 | 
					public class DownloaderFactory {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static LocalBroadcastManager localBroadcastManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Downloads to a temporary file, which *you must delete yourself when
 | 
					     * Downloads to a temporary file, which *you must delete yourself when
 | 
				
			||||||
     * you are done.  It is stored in {@link Context#getCacheDir()} and starts
 | 
					     * you are done.  It is stored in {@link Context#getCacheDir()} and starts
 | 
				
			||||||
@ -34,22 +30,19 @@ public class DownloaderFactory {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public static Downloader create(Context context, String urlString, File destFile)
 | 
					    public static Downloader create(Context context, String urlString, File destFile)
 | 
				
			||||||
            throws IOException {
 | 
					            throws IOException {
 | 
				
			||||||
        URL url = new URL(urlString);
 | 
					 | 
				
			||||||
        Downloader downloader;
 | 
					        Downloader downloader;
 | 
				
			||||||
        if (localBroadcastManager == null) {
 | 
					 | 
				
			||||||
            localBroadcastManager = LocalBroadcastManager.getInstance(context);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ("bluetooth".equalsIgnoreCase(url.getProtocol())) {
 | 
					        Uri uri = Uri.parse(urlString);
 | 
				
			||||||
            String macAddress = url.getHost().replace("-", ":");
 | 
					        String scheme = uri.getScheme();
 | 
				
			||||||
            downloader = new BluetoothDownloader(macAddress, url, destFile);
 | 
					        if ("bluetooth".equals(scheme)) {
 | 
				
			||||||
 | 
					            downloader = new BluetoothDownloader(uri, destFile);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            final String[] projection = {Schema.RepoTable.Cols.USERNAME, Schema.RepoTable.Cols.PASSWORD};
 | 
					            final String[] projection = {Schema.RepoTable.Cols.USERNAME, Schema.RepoTable.Cols.PASSWORD};
 | 
				
			||||||
            Repo repo = RepoProvider.Helper.findByUrl(context, Uri.parse(url.toString()), projection);
 | 
					            Repo repo = RepoProvider.Helper.findByUrl(context, uri, projection);
 | 
				
			||||||
            if (repo == null) {
 | 
					            if (repo == null) {
 | 
				
			||||||
                downloader = new HttpDownloader(url, destFile);
 | 
					                downloader = new HttpDownloader(uri, destFile);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                downloader = new HttpDownloader(url, destFile, repo.username, repo.password);
 | 
					                downloader = new HttpDownloader(uri, destFile, repo.username, repo.password);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return downloader;
 | 
					        return downloader;
 | 
				
			||||||
 | 
				
			|||||||
@ -31,7 +31,6 @@ import android.os.PatternMatcher;
 | 
				
			|||||||
import android.os.Process;
 | 
					import android.os.Process;
 | 
				
			||||||
import android.support.v4.content.LocalBroadcastManager;
 | 
					import android.support.v4.content.LocalBroadcastManager;
 | 
				
			||||||
import android.text.TextUtils;
 | 
					import android.text.TextUtils;
 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.fdroid.fdroid.ProgressListener;
 | 
					import org.fdroid.fdroid.ProgressListener;
 | 
				
			||||||
import org.fdroid.fdroid.R;
 | 
					import org.fdroid.fdroid.R;
 | 
				
			||||||
import org.fdroid.fdroid.Utils;
 | 
					import org.fdroid.fdroid.Utils;
 | 
				
			||||||
@ -42,7 +41,6 @@ import java.io.File;
 | 
				
			|||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.net.ConnectException;
 | 
					import java.net.ConnectException;
 | 
				
			||||||
import java.net.SocketTimeoutException;
 | 
					import java.net.SocketTimeoutException;
 | 
				
			||||||
import java.net.URL;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * DownloaderService is a service that handles asynchronous download requests
 | 
					 * DownloaderService is a service that handles asynchronous download requests
 | 
				
			||||||
@ -199,7 +197,7 @@ public class DownloaderService extends Service {
 | 
				
			|||||||
            downloader = DownloaderFactory.create(this, uri, localFile);
 | 
					            downloader = DownloaderFactory.create(this, uri, localFile);
 | 
				
			||||||
            downloader.setListener(new ProgressListener() {
 | 
					            downloader.setListener(new ProgressListener() {
 | 
				
			||||||
                @Override
 | 
					                @Override
 | 
				
			||||||
                public void onProgress(URL sourceUrl, int bytesRead, int totalBytes) {
 | 
					                public void onProgress(String urlString, long bytesRead, long totalBytes) {
 | 
				
			||||||
                    Intent intent = new Intent(Downloader.ACTION_PROGRESS);
 | 
					                    Intent intent = new Intent(Downloader.ACTION_PROGRESS);
 | 
				
			||||||
                    intent.setData(uri);
 | 
					                    intent.setData(uri);
 | 
				
			||||||
                    intent.putExtra(Downloader.EXTRA_BYTES_READ, bytesRead);
 | 
					                    intent.putExtra(Downloader.EXTRA_BYTES_READ, bytesRead);
 | 
				
			||||||
@ -321,7 +319,7 @@ public class DownloaderService extends Service {
 | 
				
			|||||||
     * Check if a URL is actively being downloaded.
 | 
					     * Check if a URL is actively being downloaded.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private static boolean isActive(String urlString) {
 | 
					    private static boolean isActive(String urlString) {
 | 
				
			||||||
        return downloader != null && TextUtils.equals(urlString, downloader.sourceUrl.toString());
 | 
					        return downloader != null && TextUtils.equals(urlString, downloader.urlString);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static void setTimeout(int ms) {
 | 
					    public static void setTimeout(int ms) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,8 @@
 | 
				
			|||||||
package org.fdroid.fdroid.net;
 | 
					package org.fdroid.fdroid.net;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.annotation.TargetApi;
 | 
				
			||||||
 | 
					import android.net.Uri;
 | 
				
			||||||
 | 
					import android.os.Build;
 | 
				
			||||||
import android.text.TextUtils;
 | 
					import android.text.TextUtils;
 | 
				
			||||||
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
 | 
					import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
 | 
				
			||||||
import info.guardianproject.netcipher.NetCipher;
 | 
					import info.guardianproject.netcipher.NetCipher;
 | 
				
			||||||
@ -27,29 +30,30 @@ public class HttpDownloader extends Downloader {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private final String username;
 | 
					    private final String username;
 | 
				
			||||||
    private final String password;
 | 
					    private final String password;
 | 
				
			||||||
 | 
					    private URL sourceUrl;
 | 
				
			||||||
    private HttpURLConnection connection;
 | 
					    private HttpURLConnection connection;
 | 
				
			||||||
    private boolean newFileAvailableOnServer;
 | 
					    private boolean newFileAvailableOnServer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    HttpDownloader(URL url, File destFile)
 | 
					    HttpDownloader(Uri uri, File destFile)
 | 
				
			||||||
            throws FileNotFoundException, MalformedURLException {
 | 
					            throws FileNotFoundException, MalformedURLException {
 | 
				
			||||||
        this(url, destFile, null, null);
 | 
					        this(uri, destFile, null, null);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Create a downloader that can authenticate via HTTP Basic Auth using the supplied
 | 
					     * Create a downloader that can authenticate via HTTP Basic Auth using the supplied
 | 
				
			||||||
     * {@code username} and {@code password}.
 | 
					     * {@code username} and {@code password}.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param url      The file to download
 | 
					     * @param uri      The file to download
 | 
				
			||||||
     * @param destFile Where the download is saved
 | 
					     * @param destFile Where the download is saved
 | 
				
			||||||
     * @param username Username for HTTP Basic Auth, use {@code null} to ignore
 | 
					     * @param username Username for HTTP Basic Auth, use {@code null} to ignore
 | 
				
			||||||
     * @param password Password for HTTP Basic Auth, use {@code null} to ignore
 | 
					     * @param password Password for HTTP Basic Auth, use {@code null} to ignore
 | 
				
			||||||
     * @throws FileNotFoundException
 | 
					     * @throws FileNotFoundException
 | 
				
			||||||
     * @throws MalformedURLException
 | 
					     * @throws MalformedURLException
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    HttpDownloader(URL url, File destFile, String username, String password)
 | 
					    HttpDownloader(Uri uri, File destFile, String username, String password)
 | 
				
			||||||
            throws FileNotFoundException, MalformedURLException {
 | 
					            throws FileNotFoundException, MalformedURLException {
 | 
				
			||||||
        super(url, destFile);
 | 
					        super(uri, destFile);
 | 
				
			||||||
 | 
					        this.sourceUrl = new URL(urlString);
 | 
				
			||||||
        this.username = username;
 | 
					        this.username = username;
 | 
				
			||||||
        this.password = password;
 | 
					        this.password = password;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -93,7 +97,7 @@ public class HttpDownloader extends Downloader {
 | 
				
			|||||||
            case 200:
 | 
					            case 200:
 | 
				
			||||||
                contentLength = tmpConn.getContentLength();
 | 
					                contentLength = tmpConn.getContentLength();
 | 
				
			||||||
                if (!TextUtils.isEmpty(etag) && etag.equals(cacheTag)) {
 | 
					                if (!TextUtils.isEmpty(etag) && etag.equals(cacheTag)) {
 | 
				
			||||||
                    Utils.debugLog(TAG, sourceUrl + " is cached, not downloading");
 | 
					                    Utils.debugLog(TAG, urlString + " is cached, not downloading");
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                newFileAvailableOnServer = true;
 | 
					                newFileAvailableOnServer = true;
 | 
				
			||||||
@ -102,7 +106,7 @@ public class HttpDownloader extends Downloader {
 | 
				
			|||||||
                notFound = true;
 | 
					                notFound = true;
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            default:
 | 
					            default:
 | 
				
			||||||
                Utils.debugLog(TAG, "HEAD check of " + sourceUrl + " returned " + statusCode + ": "
 | 
					                Utils.debugLog(TAG, "HEAD check of " + urlString + " returned " + statusCode + ": "
 | 
				
			||||||
                        + tmpConn.getResponseMessage());
 | 
					                        + tmpConn.getResponseMessage());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -116,7 +120,7 @@ public class HttpDownloader extends Downloader {
 | 
				
			|||||||
            resumable = true;
 | 
					            resumable = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        setupConnection(resumable);
 | 
					        setupConnection(resumable);
 | 
				
			||||||
        Utils.debugLog(TAG, "downloading " + sourceUrl + " (is resumable: " + resumable + ")");
 | 
					        Utils.debugLog(TAG, "downloading " + urlString + " (is resumable: " + resumable + ")");
 | 
				
			||||||
        downloadFromStream(8192, resumable);
 | 
					        downloadFromStream(8192, resumable);
 | 
				
			||||||
        cacheTag = connection.getHeaderField(HEADER_FIELD_ETAG);
 | 
					        cacheTag = connection.getHeaderField(HEADER_FIELD_ETAG);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -169,8 +173,13 @@ public class HttpDownloader extends Downloader {
 | 
				
			|||||||
    // because as the repo grows, the tradeoff will
 | 
					    // because as the repo grows, the tradeoff will
 | 
				
			||||||
    // become more worth it.
 | 
					    // become more worth it.
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public int totalDownloadSize() {
 | 
					    @TargetApi(24)
 | 
				
			||||||
        return connection.getContentLength();
 | 
					    public long totalDownloadSize() {
 | 
				
			||||||
 | 
					        if (Build.VERSION.SDK_INT < 24) {
 | 
				
			||||||
 | 
					            return connection.getContentLength();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return connection.getContentLengthLong();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
 | 
				
			|||||||
@ -3,13 +3,13 @@ package org.fdroid.fdroid.net.bluetooth;
 | 
				
			|||||||
public class FileDetails {
 | 
					public class FileDetails {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private String cacheTag;
 | 
					    private String cacheTag;
 | 
				
			||||||
    private int fileSize;
 | 
					    private long fileSize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public String getCacheTag() {
 | 
					    public String getCacheTag() {
 | 
				
			||||||
        return cacheTag;
 | 
					        return cacheTag;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public int getFileSize() {
 | 
					    public long getFileSize() {
 | 
				
			||||||
        return fileSize;
 | 
					        return fileSize;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -19,6 +19,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package org.fdroid.fdroid.views;
 | 
					package org.fdroid.fdroid.views;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.annotation.SuppressLint;
 | 
				
			||||||
import android.content.ContentValues;
 | 
					import android.content.ContentValues;
 | 
				
			||||||
import android.content.Context;
 | 
					import android.content.Context;
 | 
				
			||||||
import android.content.DialogInterface;
 | 
					import android.content.DialogInterface;
 | 
				
			||||||
@ -61,22 +62,25 @@ import org.fdroid.fdroid.data.Repo;
 | 
				
			|||||||
import org.fdroid.fdroid.data.RepoProvider;
 | 
					import org.fdroid.fdroid.data.RepoProvider;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.RepoTable;
 | 
					import org.fdroid.fdroid.data.Schema.RepoTable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.File;
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.net.HttpURLConnection;
 | 
					import java.net.HttpURLConnection;
 | 
				
			||||||
import java.net.MalformedURLException;
 | 
					import java.net.MalformedURLException;
 | 
				
			||||||
import java.net.URI;
 | 
					import java.net.URI;
 | 
				
			||||||
import java.net.URISyntaxException;
 | 
					import java.net.URISyntaxException;
 | 
				
			||||||
import java.net.URL;
 | 
					import java.net.URL;
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
import java.util.Locale;
 | 
					import java.util.Locale;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@SuppressWarnings("LineLength")
 | 
					public class ManageReposActivity extends AppCompatActivity
 | 
				
			||||||
public class ManageReposActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor>, RepoAdapter.EnabledListener {
 | 
					        implements LoaderManager.LoaderCallbacks<Cursor>, RepoAdapter.EnabledListener {
 | 
				
			||||||
    private static final String TAG = "ManageReposActivity";
 | 
					    private static final String TAG = "ManageReposActivity";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static final String DEFAULT_NEW_REPO_TEXT = "https://";
 | 
					    private static final String DEFAULT_NEW_REPO_TEXT = "https://";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private enum AddRepoState {
 | 
					    private enum AddRepoState {
 | 
				
			||||||
        DOESNT_EXIST, EXISTS_FINGERPRINT_MISMATCH, EXISTS_FINGERPRINT_MATCH,
 | 
					        DOESNT_EXIST, EXISTS_FINGERPRINT_MISMATCH, EXISTS_ADD_MIRROR,
 | 
				
			||||||
        EXISTS_DISABLED, EXISTS_ENABLED, EXISTS_UPGRADABLE_TO_SIGNED, INVALID_URL,
 | 
					        EXISTS_DISABLED, EXISTS_ENABLED, EXISTS_UPGRADABLE_TO_SIGNED, INVALID_URL,
 | 
				
			||||||
        IS_SWAP
 | 
					        IS_SWAP
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -213,18 +217,35 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
 | 
				
			|||||||
    private class AddRepo {
 | 
					    private class AddRepo {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private final Context context;
 | 
					        private final Context context;
 | 
				
			||||||
 | 
					        private final HashMap<String, Repo> urlRepoMap = new HashMap<>();
 | 
				
			||||||
 | 
					        private final HashMap<String, Repo> fingerprintRepoMap = new HashMap<>();
 | 
				
			||||||
        private final AlertDialog addRepoDialog;
 | 
					        private final AlertDialog addRepoDialog;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        private final TextView overwriteMessage;
 | 
					        private final TextView overwriteMessage;
 | 
				
			||||||
        private final ColorStateList defaultTextColour;
 | 
					        private final ColorStateList defaultTextColour;
 | 
				
			||||||
        private final Button addButton;
 | 
					        private final Button addButton;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private AddRepoState addRepoState;
 | 
					        private AddRepoState addRepoState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * Create new instance, setup GUI, and build maps for quickly looking
 | 
				
			||||||
 | 
					         * up repos based on URL or fingerprint.  These need to be in maps
 | 
				
			||||||
 | 
					         * since the user input is validated as they are typing.  This also
 | 
				
			||||||
 | 
					         * checks that the repo type matches, e.g. "repo" or "archive".
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
        AddRepo(String newAddress, String newFingerprint, final String username, final String password) {
 | 
					        AddRepo(String newAddress, String newFingerprint, final String username, final String password) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            context = ManageReposActivity.this;
 | 
					            context = ManageReposActivity.this;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (Repo repo : RepoProvider.Helper.all(context)) {
 | 
				
			||||||
 | 
					                urlRepoMap.put(repo.address, repo);
 | 
				
			||||||
 | 
					                for (String url : repo.getMirrorList()) {
 | 
				
			||||||
 | 
					                    urlRepoMap.put(url, repo);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (TextUtils.equals(getRepoType(newAddress), getRepoType(repo.address))) {
 | 
				
			||||||
 | 
					                    fingerprintRepoMap.put(repo.fingerprint, repo);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            final View view = getLayoutInflater().inflate(R.layout.addrepo, null);
 | 
					            final View view = getLayoutInflater().inflate(R.layout.addrepo, null);
 | 
				
			||||||
            addRepoDialog = new AlertDialog.Builder(context).setView(view).create();
 | 
					            addRepoDialog = new AlertDialog.Builder(context).setView(view).create();
 | 
				
			||||||
            final EditText uriEditText = (EditText) view.findViewById(R.id.edit_uri);
 | 
					            final EditText uriEditText = (EditText) view.findViewById(R.id.edit_uri);
 | 
				
			||||||
@ -275,7 +296,7 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
 | 
				
			|||||||
                            try {
 | 
					                            try {
 | 
				
			||||||
                                url = normalizeUrl(url);
 | 
					                                url = normalizeUrl(url);
 | 
				
			||||||
                            } catch (URISyntaxException e) {
 | 
					                            } catch (URISyntaxException e) {
 | 
				
			||||||
                                invalidUrl();
 | 
					                                invalidUrl(null);
 | 
				
			||||||
                                return;
 | 
					                                return;
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -289,7 +310,8 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
 | 
				
			|||||||
                                    break;
 | 
					                                    break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                case IS_SWAP:
 | 
					                                case IS_SWAP:
 | 
				
			||||||
                                    Utils.debugLog(TAG, "Removing existing swap repo " + url + " before adding new repo.");
 | 
					                                    Utils.debugLog(TAG, "Removing existing swap repo " + url
 | 
				
			||||||
 | 
					                                            + " before adding new repo.");
 | 
				
			||||||
                                    Repo repo = RepoProvider.Helper.findByAddress(context, url);
 | 
					                                    Repo repo = RepoProvider.Helper.findByAddress(context, url);
 | 
				
			||||||
                                    RepoProvider.Helper.remove(context, repo.getId());
 | 
					                                    RepoProvider.Helper.remove(context, repo.getId());
 | 
				
			||||||
                                    prepareToCreateNewRepo(url, fp, username, password);
 | 
					                                    prepareToCreateNewRepo(url, fp, username, password);
 | 
				
			||||||
@ -297,7 +319,7 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                                case EXISTS_DISABLED:
 | 
					                                case EXISTS_DISABLED:
 | 
				
			||||||
                                case EXISTS_UPGRADABLE_TO_SIGNED:
 | 
					                                case EXISTS_UPGRADABLE_TO_SIGNED:
 | 
				
			||||||
                                case EXISTS_FINGERPRINT_MATCH:
 | 
					                                case EXISTS_ADD_MIRROR:
 | 
				
			||||||
                                    updateAndEnableExistingRepo(url, fp);
 | 
					                                    updateAndEnableExistingRepo(url, fp);
 | 
				
			||||||
                                    finishedAddingRepo();
 | 
					                                    finishedAddingRepo();
 | 
				
			||||||
                                    break;
 | 
					                                    break;
 | 
				
			||||||
@ -347,9 +369,31 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
 | 
				
			|||||||
            validateRepoDetails(newAddress == null ? "" : newAddress, newFingerprint == null ? "" : newFingerprint);
 | 
					            validateRepoDetails(newAddress == null ? "" : newAddress, newFingerprint == null ? "" : newFingerprint);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * Gets the repo type as represented by the final segment of the path. This is
 | 
				
			||||||
 | 
					         * a bit trickier with {@code content://} URLs, since they might have
 | 
				
			||||||
 | 
					         * encoded "/" chars in it, for example:
 | 
				
			||||||
 | 
					         * {@code content://authority/tree/313E-1F1C%3A/document/313E-1F1C%3Aguardianproject.info%2Ffdroid%2Frepo}
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        private String getRepoType(String url) {
 | 
				
			||||||
 | 
					            String last = Uri.parse(url).getLastPathSegment();
 | 
				
			||||||
 | 
					            if (last == null) {
 | 
				
			||||||
 | 
					                return "";
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                return new File(last).getName();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * Compare the repo and the fingerprint against existing repositories, to see if this
 | 
					         * Compare the repo and the fingerprint against existing repositories, to see if this
 | 
				
			||||||
         * repo matches and display a relevant message to the user if that is the case.
 | 
					         * repo matches and display a relevant message to the user if that is the case. There
 | 
				
			||||||
 | 
					         * are many different cases to handle:
 | 
				
			||||||
 | 
					         * <ul>
 | 
				
			||||||
 | 
					         * <li> a signed repo with a {@link Repo#address URL} and fingerprint that matches
 | 
				
			||||||
 | 
					         * <li> a signed repo with a matching fingerprint and URL that matches a mirror
 | 
				
			||||||
 | 
					         * <li> a signed repo with a matching fingerprint, but the URL doesn't match any known mirror
 | 
				
			||||||
 | 
					         * <li>an unsigned repo and no fingerprint was supplied
 | 
				
			||||||
 | 
					         * </ul>
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        private void validateRepoDetails(@NonNull String uri, @NonNull String fingerprint) {
 | 
					        private void validateRepoDetails(@NonNull String uri, @NonNull String fingerprint) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -361,72 +405,84 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
 | 
				
			|||||||
                // to the user until they try to save the repo.
 | 
					                // to the user until they try to save the repo.
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            final Repo repo = !TextUtils.isEmpty(uri) ? RepoProvider.Helper.findByAddress(context, uri) : null;
 | 
					            Repo repo = fingerprintRepoMap.get(fingerprint);
 | 
				
			||||||
 | 
					            if (repo == null) {
 | 
				
			||||||
 | 
					                repo = urlRepoMap.get(uri);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (repo == null) {
 | 
					            if (repo == null) {
 | 
				
			||||||
                repoDoesntExist();
 | 
					                repoDoesntExist(repo);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                if (repo.isSwap) {
 | 
					                if (repo.isSwap) {
 | 
				
			||||||
                    repoIsSwap();
 | 
					                    repoIsSwap(repo);
 | 
				
			||||||
                } else if (repo.fingerprint == null && fingerprint.length() > 0) {
 | 
					                } else if (repo.fingerprint == null && fingerprint.length() > 0) {
 | 
				
			||||||
                    upgradingToSigned();
 | 
					                    upgradingToSigned(repo);
 | 
				
			||||||
                } else if (repo.fingerprint != null && !repo.fingerprint.equalsIgnoreCase(fingerprint)) {
 | 
					                } else if (repo.fingerprint != null && !repo.fingerprint.equalsIgnoreCase(fingerprint)) {
 | 
				
			||||||
                    repoFingerprintDoesntMatch();
 | 
					                    repoFingerprintDoesntMatch(repo);
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    // Could be either an unsigned repo, and no fingerprint was supplied,
 | 
					                    if (!TextUtils.equals(repo.address, uri)
 | 
				
			||||||
                    // or it could be a signed repo with a matching fingerprint.
 | 
					                            && !repo.getMirrorList().contains(uri)) {
 | 
				
			||||||
                    if (repo.inuse) {
 | 
					                        repoExistsAddMirror(repo);
 | 
				
			||||||
                        repoExistsAndEnabled();
 | 
					                    } else if (repo.inuse) {
 | 
				
			||||||
 | 
					                        repoExistsAndEnabled(repo);
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        repoExistsAndDisabled();
 | 
					                        repoExistsAndDisabled(repo);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void repoDoesntExist() {
 | 
					        private void repoDoesntExist(Repo repo) {
 | 
				
			||||||
            updateUi(AddRepoState.DOESNT_EXIST, 0, false, R.string.repo_add_add, true);
 | 
					            updateUi(repo, AddRepoState.DOESNT_EXIST, 0, false, R.string.repo_add_add, true);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void repoIsSwap() {
 | 
					        private void repoIsSwap(Repo repo) {
 | 
				
			||||||
            updateUi(AddRepoState.IS_SWAP, 0, false, R.string.repo_add_add, true);
 | 
					            updateUi(repo, AddRepoState.IS_SWAP, 0, false, R.string.repo_add_add, true);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * Same address with different fingerprint, this could be malicious, so display a message
 | 
					         * Same address with different fingerprint, this could be malicious, so display a message
 | 
				
			||||||
         * force the user to manually delete the repo before adding this one.
 | 
					         * force the user to manually delete the repo before adding this one.
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        private void repoFingerprintDoesntMatch() {
 | 
					        private void repoFingerprintDoesntMatch(Repo repo) {
 | 
				
			||||||
            updateUi(AddRepoState.EXISTS_FINGERPRINT_MISMATCH, R.string.repo_delete_to_overwrite,
 | 
					            updateUi(repo, AddRepoState.EXISTS_FINGERPRINT_MISMATCH,
 | 
				
			||||||
 | 
					                    R.string.repo_delete_to_overwrite,
 | 
				
			||||||
                    true, R.string.overwrite, false);
 | 
					                    true, R.string.overwrite, false);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void invalidUrl() {
 | 
					        private void invalidUrl(Repo repo) {
 | 
				
			||||||
            updateUi(AddRepoState.INVALID_URL, R.string.invalid_url, true,
 | 
					            updateUi(repo, AddRepoState.INVALID_URL, R.string.invalid_url, true,
 | 
				
			||||||
                    R.string.repo_add_add, false);
 | 
					                    R.string.repo_add_add, false);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void repoExistsAndDisabled() {
 | 
					        private void repoExistsAndDisabled(Repo repo) {
 | 
				
			||||||
            updateUi(AddRepoState.EXISTS_DISABLED,
 | 
					            updateUi(repo, AddRepoState.EXISTS_DISABLED,
 | 
				
			||||||
                    R.string.repo_exists_enable, false, R.string.enable, true);
 | 
					                    R.string.repo_exists_enable, false, R.string.enable, true);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void repoExistsAndEnabled() {
 | 
					        private void repoExistsAndEnabled(Repo repo) {
 | 
				
			||||||
            updateUi(AddRepoState.EXISTS_ENABLED, R.string.repo_exists_and_enabled, false,
 | 
					            updateUi(repo, AddRepoState.EXISTS_ENABLED, R.string.repo_exists_and_enabled, false,
 | 
				
			||||||
                    R.string.ok, true);
 | 
					                    R.string.ok, true);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void upgradingToSigned() {
 | 
					        private void repoExistsAddMirror(Repo repo) {
 | 
				
			||||||
            updateUi(AddRepoState.EXISTS_UPGRADABLE_TO_SIGNED, R.string.repo_exists_add_fingerprint,
 | 
					            updateUi(repo, AddRepoState.EXISTS_ADD_MIRROR, R.string.repo_exists_add_mirror, false,
 | 
				
			||||||
 | 
					                    R.string.repo_add_mirror, 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);
 | 
					                    false, R.string.add_key, true);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void updateUi(AddRepoState state, int messageRes, boolean redMessage, int addTextRes, boolean addEnabled) {
 | 
					        @DebugLog
 | 
				
			||||||
 | 
					        private void updateUi(Repo repo, AddRepoState state, int messageRes, boolean redMessage, int addTextRes,
 | 
				
			||||||
 | 
					                              boolean addEnabled) {
 | 
				
			||||||
            if (addRepoState != state) {
 | 
					            if (addRepoState != state) {
 | 
				
			||||||
                addRepoState = state;
 | 
					                addRepoState = state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (messageRes > 0) {
 | 
					                if (messageRes > 0) {
 | 
				
			||||||
                    overwriteMessage.setText(messageRes);
 | 
					                    overwriteMessage.setText(String.format(getString(messageRes), repo.name));
 | 
				
			||||||
                    overwriteMessage.setVisibility(View.VISIBLE);
 | 
					                    overwriteMessage.setVisibility(View.VISIBLE);
 | 
				
			||||||
                    if (redMessage) {
 | 
					                    if (redMessage) {
 | 
				
			||||||
                        overwriteMessage.setTextColor(getResources().getColor(R.color.red));
 | 
					                        overwriteMessage.setTextColor(getResources().getColor(R.color.red));
 | 
				
			||||||
@ -445,30 +501,50 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
 | 
				
			|||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * Adds a new repo to the database.
 | 
					         * Adds a new repo to the database.
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        private void prepareToCreateNewRepo(final String originalAddress, final String fingerprint, final String username, final String password) {
 | 
					        @SuppressLint("StaticFieldLeak")
 | 
				
			||||||
 | 
					        private void prepareToCreateNewRepo(final String originalAddress, final String fingerprint,
 | 
				
			||||||
 | 
					                                            final String username, final String password) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            addRepoDialog.findViewById(R.id.add_repo_form).setVisibility(View.GONE);
 | 
					            final View addRepoForm = addRepoDialog.findViewById(R.id.add_repo_form);
 | 
				
			||||||
            addRepoDialog.getButton(AlertDialog.BUTTON_POSITIVE).setVisibility(View.GONE);
 | 
					            addRepoForm.setVisibility(View.GONE);
 | 
				
			||||||
 | 
					            final View positiveButton = addRepoDialog.getButton(AlertDialog.BUTTON_POSITIVE);
 | 
				
			||||||
 | 
					            positiveButton.setVisibility(View.GONE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            final TextView textSearching = (TextView) addRepoDialog.findViewById(R.id.text_searching_for_repo);
 | 
					            final TextView textSearching = (TextView) addRepoDialog.findViewById(R.id.text_searching_for_repo);
 | 
				
			||||||
            textSearching.setText(getString(R.string.repo_searching_address, originalAddress));
 | 
					            textSearching.setText(getString(R.string.repo_searching_address, originalAddress));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            final Button skip = addRepoDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
 | 
				
			||||||
 | 
					            skip.setText(R.string.skip);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            final AsyncTask<String, String, String> checker = new AsyncTask<String, String, String>() {
 | 
					            final AsyncTask<String, String, String> checker = new AsyncTask<String, String, String>() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                private int statusCode = -1;
 | 
					                private int statusCode = -1;
 | 
				
			||||||
 | 
					                private final static int REFRESH_DIALOG = Integer.MAX_VALUE;
 | 
				
			||||||
 | 
					                private final static int HTTP_UNAUTHORIZED = 401;
 | 
				
			||||||
 | 
					                private final static int HTTP_OK = 200;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                @Override
 | 
					                @Override
 | 
				
			||||||
                protected String doInBackground(String... params) {
 | 
					                protected String doInBackground(String... params) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    final String originalAddress = params[0];
 | 
					                    final String originalAddress = params[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (fingerprintRepoMap.containsKey(fingerprint)) {
 | 
				
			||||||
 | 
					                        statusCode = REFRESH_DIALOG;
 | 
				
			||||||
 | 
					                        return originalAddress;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    final String[] pathsToCheck = {"", "fdroid/repo", "repo"};
 | 
					                    final String[] pathsToCheck = {"", "fdroid/repo", "repo"};
 | 
				
			||||||
                    for (final String path : pathsToCheck) {
 | 
					                    for (final String path : pathsToCheck) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        Utils.debugLog(TAG, "Checking for repo at " + originalAddress + " with suffix \"" + path + "\".");
 | 
					                        Utils.debugLog(TAG, "Check for repo at " + originalAddress + " with suffix '" + path + "'");
 | 
				
			||||||
                        Uri.Builder builder = Uri.parse(originalAddress).buildUpon().appendEncodedPath(path);
 | 
					                        Uri.Builder builder = Uri.parse(originalAddress).buildUpon().appendEncodedPath(path);
 | 
				
			||||||
                        final String addressWithoutIndex = builder.build().toString();
 | 
					                        final String addressWithoutIndex = builder.build().toString();
 | 
				
			||||||
                        publishProgress(addressWithoutIndex);
 | 
					                        publishProgress(addressWithoutIndex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (urlRepoMap.containsKey(addressWithoutIndex)) {
 | 
				
			||||||
 | 
					                            statusCode = REFRESH_DIALOG;
 | 
				
			||||||
 | 
					                            return addressWithoutIndex;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        final Uri uri = builder.appendPath("index.jar").build();
 | 
					                        final Uri uri = builder.appendPath("index.jar").build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        try {
 | 
					                        try {
 | 
				
			||||||
@ -482,7 +558,7 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
 | 
				
			|||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        if (isCancelled()) {
 | 
					                        if (isCancelled()) {
 | 
				
			||||||
                            Utils.debugLog(TAG, "Not checking any more repo addresses, because process was skipped.");
 | 
					                            Utils.debugLog(TAG, "Not checking more repo addresses, because process was skipped.");
 | 
				
			||||||
                            break;
 | 
					                            break;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@ -491,14 +567,13 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                private boolean checkForRepository(Uri indexUri) throws IOException {
 | 
					                private boolean checkForRepository(Uri indexUri) throws IOException {
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    final URL url = new URL(indexUri.toString());
 | 
					                    final URL url = new URL(indexUri.toString());
 | 
				
			||||||
                    final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
 | 
					                    final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
 | 
				
			||||||
                    connection.setRequestMethod("HEAD");
 | 
					                    connection.setRequestMethod("HEAD");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    statusCode = connection.getResponseCode();
 | 
					                    statusCode = connection.getResponseCode();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    return statusCode == 401 || statusCode == 200;
 | 
					                    return statusCode == HTTP_UNAUTHORIZED || statusCode == HTTP_OK;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                @Override
 | 
					                @Override
 | 
				
			||||||
@ -512,10 +587,11 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    if (addRepoDialog.isShowing()) {
 | 
					                    if (addRepoDialog.isShowing()) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        if (statusCode == 401) {
 | 
					                        if (statusCode == HTTP_UNAUTHORIZED) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            final View view = getLayoutInflater().inflate(R.layout.login, null);
 | 
					                            final View view = getLayoutInflater().inflate(R.layout.login, null);
 | 
				
			||||||
                            final AlertDialog credentialsDialog = new AlertDialog.Builder(context).setView(view).create();
 | 
					                            final AlertDialog credentialsDialog = new AlertDialog.Builder(context)
 | 
				
			||||||
 | 
					                                    .setView(view).create();
 | 
				
			||||||
                            final EditText nameInput = (EditText) view.findViewById(R.id.edit_name);
 | 
					                            final EditText nameInput = (EditText) view.findViewById(R.id.edit_name);
 | 
				
			||||||
                            final EditText passwordInput = (EditText) view.findViewById(R.id.edit_password);
 | 
					                            final EditText passwordInput = (EditText) view.findViewById(R.id.edit_password);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -543,12 +619,21 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
 | 
				
			|||||||
                                    new DialogInterface.OnClickListener() {
 | 
					                                    new DialogInterface.OnClickListener() {
 | 
				
			||||||
                                        @Override
 | 
					                                        @Override
 | 
				
			||||||
                                        public void onClick(DialogInterface dialog, int which) {
 | 
					                                        public void onClick(DialogInterface dialog, int which) {
 | 
				
			||||||
                                            createNewRepo(newAddress, fingerprint, nameInput.getText().toString(), passwordInput.getText().toString());
 | 
					                                            createNewRepo(newAddress, fingerprint,
 | 
				
			||||||
 | 
					                                                    nameInput.getText().toString(),
 | 
				
			||||||
 | 
					                                                    passwordInput.getText().toString());
 | 
				
			||||||
                                        }
 | 
					                                        }
 | 
				
			||||||
                                    });
 | 
					                                    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            credentialsDialog.show();
 | 
					                            credentialsDialog.show();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        } else if (statusCode == REFRESH_DIALOG) {
 | 
				
			||||||
 | 
					                            addRepoForm.setVisibility(View.VISIBLE);
 | 
				
			||||||
 | 
					                            positiveButton.setVisibility(View.VISIBLE);
 | 
				
			||||||
 | 
					                            textSearching.setText("");
 | 
				
			||||||
 | 
					                            skip.setText(R.string.cancel);
 | 
				
			||||||
 | 
					                            skip.setOnClickListener(null);
 | 
				
			||||||
 | 
					                            validateRepoDetails(newAddress, fingerprint);
 | 
				
			||||||
                        } else {
 | 
					                        } else {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            // create repo without username/password
 | 
					                            // create repo without username/password
 | 
				
			||||||
@ -558,8 +643,6 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Button skip = addRepoDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
 | 
					 | 
				
			||||||
            skip.setText(R.string.skip);
 | 
					 | 
				
			||||||
            skip.setOnClickListener(new View.OnClickListener() {
 | 
					            skip.setOnClickListener(new View.OnClickListener() {
 | 
				
			||||||
                @Override
 | 
					                @Override
 | 
				
			||||||
                public void onClick(View v) {
 | 
					                public void onClick(View v) {
 | 
				
			||||||
@ -615,7 +698,8 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
 | 
				
			|||||||
            createNewRepo(address, fingerprint, null, null);
 | 
					            createNewRepo(address, fingerprint, null, null);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void createNewRepo(String address, String fingerprint, final String username, final String password) {
 | 
					        private void createNewRepo(String address, String fingerprint,
 | 
				
			||||||
 | 
					                                   final String username, final String password) {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                address = normalizeUrl(address);
 | 
					                address = normalizeUrl(address);
 | 
				
			||||||
            } catch (URISyntaxException e) {
 | 
					            } catch (URISyntaxException e) {
 | 
				
			||||||
@ -634,7 +718,7 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            RepoProvider.Helper.insert(context, values);
 | 
					            RepoProvider.Helper.insert(context, values);
 | 
				
			||||||
            finishedAddingRepo();
 | 
					            finishedAddingRepo();
 | 
				
			||||||
            Toast.makeText(ManageReposActivity.this, getString(R.string.repo_added, address), Toast.LENGTH_SHORT).show();
 | 
					            Toast.makeText(context, getString(R.string.repo_added, address), Toast.LENGTH_SHORT).show();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
@ -651,11 +735,34 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Utils.debugLog(TAG, "Enabling existing repo: " + url);
 | 
					            Utils.debugLog(TAG, "Enabling existing repo: " + url);
 | 
				
			||||||
            Repo repo = RepoProvider.Helper.findByAddress(context, url);
 | 
					            Repo repo = fingerprintRepoMap.get(fingerprint);
 | 
				
			||||||
 | 
					            if (repo == null) {
 | 
				
			||||||
 | 
					                repo = RepoProvider.Helper.findByAddress(context, url);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ContentValues values = new ContentValues(2);
 | 
					            ContentValues values = new ContentValues(2);
 | 
				
			||||||
            values.put(RepoTable.Cols.IN_USE, 1);
 | 
					            values.put(RepoTable.Cols.IN_USE, 1);
 | 
				
			||||||
            values.put(RepoTable.Cols.FINGERPRINT, fingerprint);
 | 
					            values.put(RepoTable.Cols.FINGERPRINT, fingerprint);
 | 
				
			||||||
 | 
					            if (!TextUtils.equals(url, repo.address)) {
 | 
				
			||||||
 | 
					                boolean addUserMirror = true;
 | 
				
			||||||
 | 
					                for (String mirror : repo.getMirrorList()) {
 | 
				
			||||||
 | 
					                    if (TextUtils.equals(mirror, url)) {
 | 
				
			||||||
 | 
					                        addUserMirror = false;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (addUserMirror) {
 | 
				
			||||||
 | 
					                    if (repo.userMirrors == null) {
 | 
				
			||||||
 | 
					                        repo.userMirrors = new String[]{url};
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        int last = repo.userMirrors.length;
 | 
				
			||||||
 | 
					                        repo.userMirrors = Arrays.copyOf(repo.userMirrors, last);
 | 
				
			||||||
 | 
					                        repo.userMirrors[last] = url;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    values.put(RepoTable.Cols.USER_MIRRORS, Utils.serializeCommaSeparatedString(repo.userMirrors));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            RepoProvider.Helper.update(context, repo, values);
 | 
					            RepoProvider.Helper.update(context, repo, values);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            notifyDataSetChanged();
 | 
					            notifyDataSetChanged();
 | 
				
			||||||
            finishedAddingRepo();
 | 
					            finishedAddingRepo();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -675,7 +782,6 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
 | 
				
			|||||||
                finish();
 | 
					                finish();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void addRepoFromIntent(Intent intent) {
 | 
					    private void addRepoFromIntent(Intent intent) {
 | 
				
			||||||
@ -683,7 +789,8 @@ public class ManageReposActivity extends AppCompatActivity implements LoaderMana
 | 
				
			|||||||
        NewRepoConfig newRepoConfig = new NewRepoConfig(this, intent);
 | 
					        NewRepoConfig newRepoConfig = new NewRepoConfig(this, intent);
 | 
				
			||||||
        if (newRepoConfig.isValidRepo()) {
 | 
					        if (newRepoConfig.isValidRepo()) {
 | 
				
			||||||
            isImportingRepo = true;
 | 
					            isImportingRepo = true;
 | 
				
			||||||
            showAddRepo(newRepoConfig.getRepoUriString(), newRepoConfig.getFingerprint(), newRepoConfig.getUsername(), newRepoConfig.getPassword());
 | 
					            showAddRepo(newRepoConfig.getRepoUriString(), newRepoConfig.getFingerprint(),
 | 
				
			||||||
 | 
					                    newRepoConfig.getUsername(), newRepoConfig.getPassword());
 | 
				
			||||||
            checkIfNewRepoOnSameWifi(newRepoConfig);
 | 
					            checkIfNewRepoOnSameWifi(newRepoConfig);
 | 
				
			||||||
        } else if (newRepoConfig.getErrorMessage() != null) {
 | 
					        } else if (newRepoConfig.getErrorMessage() != null) {
 | 
				
			||||||
            Toast.makeText(this, newRepoConfig.getErrorMessage(), Toast.LENGTH_LONG).show();
 | 
					            Toast.makeText(this, newRepoConfig.getErrorMessage(), Toast.LENGTH_LONG).show();
 | 
				
			||||||
 | 
				
			|||||||
@ -50,26 +50,26 @@ public class RepoDetailsActivity extends ActionBarActivity {
 | 
				
			|||||||
     * all of this info, otherwise they will be hidden.
 | 
					     * all of this info, otherwise they will be hidden.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private static final int[] SHOW_IF_EXISTS = {
 | 
					    private static final int[] SHOW_IF_EXISTS = {
 | 
				
			||||||
        R.id.label_repo_name,
 | 
					            R.id.label_repo_name,
 | 
				
			||||||
        R.id.text_repo_name,
 | 
					            R.id.text_repo_name,
 | 
				
			||||||
        R.id.text_description,
 | 
					            R.id.text_description,
 | 
				
			||||||
        R.id.label_num_apps,
 | 
					            R.id.label_num_apps,
 | 
				
			||||||
        R.id.text_num_apps,
 | 
					            R.id.text_num_apps,
 | 
				
			||||||
        R.id.label_last_update,
 | 
					            R.id.label_last_update,
 | 
				
			||||||
        R.id.text_last_update,
 | 
					            R.id.text_last_update,
 | 
				
			||||||
        R.id.label_username,
 | 
					            R.id.label_username,
 | 
				
			||||||
        R.id.text_username,
 | 
					            R.id.text_username,
 | 
				
			||||||
        R.id.button_edit_credentials,
 | 
					            R.id.button_edit_credentials,
 | 
				
			||||||
        R.id.label_repo_fingerprint,
 | 
					            R.id.label_repo_fingerprint,
 | 
				
			||||||
        R.id.text_repo_fingerprint,
 | 
					            R.id.text_repo_fingerprint,
 | 
				
			||||||
        R.id.text_repo_fingerprint_description,
 | 
					            R.id.text_repo_fingerprint_description,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * If the repo has <em>not</em> been updated yet, then we only show
 | 
					     * If the repo has <em>not</em> been updated yet, then we only show
 | 
				
			||||||
     * these, otherwise they are hidden.
 | 
					     * these, otherwise they are hidden.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private static final int[] HIDE_IF_EXISTS = {
 | 
					    private static final int[] HIDE_IF_EXISTS = {
 | 
				
			||||||
        R.id.text_not_yet_updated,
 | 
					            R.id.text_not_yet_updated,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    private Repo repo;
 | 
					    private Repo repo;
 | 
				
			||||||
    private long repoId;
 | 
					    private long repoId;
 | 
				
			||||||
@ -108,12 +108,19 @@ public class RepoDetailsActivity extends ActionBarActivity {
 | 
				
			|||||||
                RepoTable.Cols.NAME,
 | 
					                RepoTable.Cols.NAME,
 | 
				
			||||||
                RepoTable.Cols.ADDRESS,
 | 
					                RepoTable.Cols.ADDRESS,
 | 
				
			||||||
                RepoTable.Cols.FINGERPRINT,
 | 
					                RepoTable.Cols.FINGERPRINT,
 | 
				
			||||||
 | 
					                RepoTable.Cols.MIRRORS,
 | 
				
			||||||
 | 
					                RepoTable.Cols.USER_MIRRORS,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        repo = RepoProvider.Helper.findById(this, repoId, projection);
 | 
					        repo = RepoProvider.Helper.findById(this, repoId, projection);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        TextView inputUrl = (TextView) findViewById(R.id.input_repo_url);
 | 
					        TextView inputUrl = (TextView) findViewById(R.id.input_repo_url);
 | 
				
			||||||
        inputUrl.setText(repo.address);
 | 
					        inputUrl.setText(repo.address);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (repo.address.startsWith("content://")) {
 | 
				
			||||||
 | 
					            // no need to show a QR Code, it is not shareable
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Uri uri = Uri.parse(repo.address);
 | 
					        Uri uri = Uri.parse(repo.address);
 | 
				
			||||||
        uri = uri.buildUpon().appendQueryParameter("fingerprint", repo.fingerprint).build();
 | 
					        uri = uri.buildUpon().appendQueryParameter("fingerprint", repo.fingerprint).build();
 | 
				
			||||||
        String qrUriString = uri.toString();
 | 
					        String qrUriString = uri.toString();
 | 
				
			||||||
@ -321,6 +328,33 @@ public class RepoDetailsActivity extends ActionBarActivity {
 | 
				
			|||||||
        TextView numApps = (TextView) repoView.findViewById(R.id.text_num_apps);
 | 
					        TextView numApps = (TextView) repoView.findViewById(R.id.text_num_apps);
 | 
				
			||||||
        TextView lastUpdated = (TextView) repoView.findViewById(R.id.text_last_update);
 | 
					        TextView lastUpdated = (TextView) repoView.findViewById(R.id.text_last_update);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (repo.mirrors != null) {
 | 
				
			||||||
 | 
					            TextView officialMirrorsLabel = (TextView) repoView.findViewById(R.id.label_official_mirrors);
 | 
				
			||||||
 | 
					            officialMirrorsLabel.setVisibility(View.VISIBLE);
 | 
				
			||||||
 | 
					            TextView officialMirrorsText = (TextView) repoView.findViewById(R.id.text_official_mirrors);
 | 
				
			||||||
 | 
					            officialMirrorsText.setVisibility(View.VISIBLE);
 | 
				
			||||||
 | 
					            StringBuilder builder = new StringBuilder();
 | 
				
			||||||
 | 
					            for (String url : repo.mirrors) {
 | 
				
			||||||
 | 
					                builder.append("◦ ");
 | 
				
			||||||
 | 
					                builder.append(url);
 | 
				
			||||||
 | 
					                builder.append('\n');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            officialMirrorsText.setText(builder.toString());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (repo.userMirrors != null) {
 | 
				
			||||||
 | 
					            TextView userMirrorsLabel = (TextView) repoView.findViewById(R.id.label_user_mirrors);
 | 
				
			||||||
 | 
					            userMirrorsLabel.setVisibility(View.VISIBLE);
 | 
				
			||||||
 | 
					            TextView userMirrorsText = (TextView) repoView.findViewById(R.id.text_user_mirrors);
 | 
				
			||||||
 | 
					            userMirrorsText.setVisibility(View.VISIBLE);
 | 
				
			||||||
 | 
					            StringBuilder builder = new StringBuilder();
 | 
				
			||||||
 | 
					            for (String url : repo.userMirrors) {
 | 
				
			||||||
 | 
					                builder.append("◦ ");
 | 
				
			||||||
 | 
					                builder.append(url);
 | 
				
			||||||
 | 
					                builder.append('\n');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            userMirrorsText.setText(builder.toString());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        name.setText(repo.name);
 | 
					        name.setText(repo.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        int appCount = RepoProvider.Helper.countAppsForRepo(this, repoId);
 | 
					        int appCount = RepoProvider.Helper.countAppsForRepo(this, repoId);
 | 
				
			||||||
@ -345,22 +379,22 @@ public class RepoDetailsActivity extends ActionBarActivity {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private void promptForDelete() {
 | 
					    private void promptForDelete() {
 | 
				
			||||||
        new AlertDialog.Builder(this)
 | 
					        new AlertDialog.Builder(this)
 | 
				
			||||||
            .setTitle(R.string.repo_confirm_delete_title)
 | 
					                .setTitle(R.string.repo_confirm_delete_title)
 | 
				
			||||||
            .setMessage(R.string.repo_confirm_delete_body)
 | 
					                .setMessage(R.string.repo_confirm_delete_body)
 | 
				
			||||||
            .setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
 | 
					                .setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
 | 
				
			||||||
                @Override
 | 
					                    @Override
 | 
				
			||||||
                public void onClick(DialogInterface dialog, int which) {
 | 
					                    public void onClick(DialogInterface dialog, int which) {
 | 
				
			||||||
                    RepoProvider.Helper.remove(getApplicationContext(), repoId);
 | 
					                        RepoProvider.Helper.remove(getApplicationContext(), repoId);
 | 
				
			||||||
                    finish();
 | 
					                        finish();
 | 
				
			||||||
                }
 | 
					                    }
 | 
				
			||||||
            }).setNegativeButton(android.R.string.cancel,
 | 
					                }).setNegativeButton(android.R.string.cancel,
 | 
				
			||||||
                new DialogInterface.OnClickListener() {
 | 
					                new DialogInterface.OnClickListener() {
 | 
				
			||||||
                    @Override
 | 
					                    @Override
 | 
				
			||||||
                    public void onClick(DialogInterface dialog, int which) {
 | 
					                    public void onClick(DialogInterface dialog, int which) {
 | 
				
			||||||
                        // Do nothing...
 | 
					                        // Do nothing...
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            ).show();
 | 
					        ).show();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void showChangePasswordDialog(final View parentView) {
 | 
					    public void showChangePasswordDialog(final View parentView) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,18 +0,0 @@
 | 
				
			|||||||
package sun.net.www.protocol.bluetooth;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.io.IOException;
 | 
					 | 
				
			||||||
import java.net.URL;
 | 
					 | 
				
			||||||
import java.net.URLConnection;
 | 
					 | 
				
			||||||
import java.net.URLStreamHandler;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * This class is added so that the bluetooth:// scheme we use for the {@link
 | 
					 | 
				
			||||||
 * org.fdroid.fdroid.net.BluetoothDownloader} is not treated as invalid by
 | 
					 | 
				
			||||||
 * the {@link URL} class.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public class Handler extends URLStreamHandler {
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected URLConnection openConnection(URL u) throws IOException {
 | 
					 | 
				
			||||||
        throw new UnsupportedOperationException("openConnection() not supported on bluetooth:// URLs");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -71,6 +71,30 @@
 | 
				
			|||||||
                android:id="@+id/text_last_update"
 | 
					                android:id="@+id/text_last_update"
 | 
				
			||||||
                style="@style/BodyText" />
 | 
					                style="@style/BodyText" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <!-- mirrors included in the index -->
 | 
				
			||||||
 | 
					            <TextView
 | 
				
			||||||
 | 
					                    android:id="@+id/label_official_mirrors"
 | 
				
			||||||
 | 
					                    android:visibility="gone"
 | 
				
			||||||
 | 
					                    android:text="@string/repo_official_mirrors"
 | 
				
			||||||
 | 
					                    style="@style/CaptionText" />
 | 
				
			||||||
 | 
					            <TextView
 | 
				
			||||||
 | 
					                    android:id="@+id/text_official_mirrors"
 | 
				
			||||||
 | 
					                    android:visibility="gone"
 | 
				
			||||||
 | 
					                    android:textColor="@android:color/black"
 | 
				
			||||||
 | 
					                    style="@style/CaptionText" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <!-- mirrors added by the user -->
 | 
				
			||||||
 | 
					            <TextView
 | 
				
			||||||
 | 
					                    android:id="@+id/label_user_mirrors"
 | 
				
			||||||
 | 
					                    android:visibility="gone"
 | 
				
			||||||
 | 
					                    android:text="@string/repo_user_mirrors"
 | 
				
			||||||
 | 
					                    style="@style/CaptionText" />
 | 
				
			||||||
 | 
					            <TextView
 | 
				
			||||||
 | 
					                    android:id="@+id/text_user_mirrors"
 | 
				
			||||||
 | 
					                    android:visibility="gone"
 | 
				
			||||||
 | 
					                    android:textColor="@android:color/black"
 | 
				
			||||||
 | 
					                    style="@style/CaptionText" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <!-- The credentials used to access this repo (optional) -->
 | 
					            <!-- The credentials used to access this repo (optional) -->
 | 
				
			||||||
            <TextView
 | 
					            <TextView
 | 
				
			||||||
                android:id="@+id/label_username"
 | 
					                android:id="@+id/label_username"
 | 
				
			||||||
 | 
				
			|||||||
@ -116,6 +116,7 @@ This often occurs with apps installed via Google Play or other sources, if they
 | 
				
			|||||||
    <string name="no">No</string>
 | 
					    <string name="no">No</string>
 | 
				
			||||||
    <string name="repo_add_title">Add new repository</string>
 | 
					    <string name="repo_add_title">Add new repository</string>
 | 
				
			||||||
    <string name="repo_add_add">Add</string>
 | 
					    <string name="repo_add_add">Add</string>
 | 
				
			||||||
 | 
					    <string name="repo_add_mirror">Add mirror</string>
 | 
				
			||||||
    <string name="links">Links</string>
 | 
					    <string name="links">Links</string>
 | 
				
			||||||
    <string name="versions">Versions</string>
 | 
					    <string name="versions">Versions</string>
 | 
				
			||||||
    <string name="more">More</string>
 | 
					    <string name="more">More</string>
 | 
				
			||||||
@ -134,12 +135,11 @@ This often occurs with apps installed via Google Play or other sources, if they
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    <string name="repo_add_url">Repository address</string>
 | 
					    <string name="repo_add_url">Repository address</string>
 | 
				
			||||||
    <string name="repo_add_fingerprint">Fingerprint (optional)</string>
 | 
					    <string name="repo_add_fingerprint">Fingerprint (optional)</string>
 | 
				
			||||||
    <string name="repo_exists_add_fingerprint">This repo is already setup, this will add new key information.</string>
 | 
					    <string name="repo_exists_add_fingerprint">%1$s is already setup, this will add new key information.</string>
 | 
				
			||||||
    <string name="repo_exists_enable">This repo is already setup, confirm that you want to re-enable it.</string>
 | 
					    <string name="repo_exists_enable">%1$s is already setup, confirm that you want to re-enable it.</string>
 | 
				
			||||||
    <string name="repo_exists_and_enabled">The incoming repo is already setup and enabled.</string>
 | 
					    <string name="repo_exists_and_enabled">%1$s is already setup and enabled.</string>
 | 
				
			||||||
    <string name="repo_delete_to_overwrite">You must first delete this repo before you can add one with a different
 | 
					    <string name="repo_delete_to_overwrite">First delete %1$s in order to add this with a conflicting key.</string>
 | 
				
			||||||
        key.
 | 
					    <string name="repo_exists_add_mirror">This is a copy of %1$s, add it as a mirror?</string>
 | 
				
			||||||
    </string>
 | 
					 | 
				
			||||||
    <string name="bad_fingerprint">Bad fingerprint</string>
 | 
					    <string name="bad_fingerprint">Bad fingerprint</string>
 | 
				
			||||||
    <string name="invalid_url">This is not a valid URL.</string>
 | 
					    <string name="invalid_url">This is not a valid URL.</string>
 | 
				
			||||||
    <string name="malformed_repo_uri">Ignoring malformed repo URI: %s</string>
 | 
					    <string name="malformed_repo_uri">Ignoring malformed repo URI: %s</string>
 | 
				
			||||||
@ -307,6 +307,8 @@ This often occurs with apps installed via Google Play or other sources, if they
 | 
				
			|||||||
    <string name="repo_fingerprint">Fingerprint of the signing key (SHA-256)</string>
 | 
					    <string name="repo_fingerprint">Fingerprint of the signing key (SHA-256)</string>
 | 
				
			||||||
    <string name="repo_description">Description</string>
 | 
					    <string name="repo_description">Description</string>
 | 
				
			||||||
    <string name="repo_last_update">Last update</string>
 | 
					    <string name="repo_last_update">Last update</string>
 | 
				
			||||||
 | 
					    <string name="repo_official_mirrors">Official mirrors</string>
 | 
				
			||||||
 | 
					    <string name="repo_user_mirrors">User mirrors</string>
 | 
				
			||||||
    <string name="repo_name">Name</string>
 | 
					    <string name="repo_name">Name</string>
 | 
				
			||||||
    <string name="unsigned_description">This means that the list of
 | 
					    <string name="unsigned_description">This means that the list of
 | 
				
			||||||
        apps could not be verified. You should be careful
 | 
					        apps could not be verified. You should be careful
 | 
				
			||||||
 | 
				
			|||||||
@ -158,22 +158,17 @@ public abstract class MultiRepoUpdaterTest extends FDroidProviderTest {
 | 
				
			|||||||
        return createRepo(name, uri, context, PUB_KEY);
 | 
					        return createRepo(name, uri, context, PUB_KEY);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Creates a real instance of {@code Repo} by loading it from the database,
 | 
				
			||||||
 | 
					     * that ensures it includes the primary key from the database.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    static Repo createRepo(String name, String uri, Context context, String signingCert) {
 | 
					    static Repo createRepo(String name, String uri, Context context, String signingCert) {
 | 
				
			||||||
        Repo repo = new Repo();
 | 
					 | 
				
			||||||
        repo.signingCertificate = signingCert;
 | 
					 | 
				
			||||||
        repo.address = uri;
 | 
					 | 
				
			||||||
        repo.name = name;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ContentValues values = new ContentValues(3);
 | 
					        ContentValues values = new ContentValues(3);
 | 
				
			||||||
        values.put(Schema.RepoTable.Cols.SIGNING_CERT, repo.signingCertificate);
 | 
					        values.put(Schema.RepoTable.Cols.SIGNING_CERT, signingCert);
 | 
				
			||||||
        values.put(Schema.RepoTable.Cols.ADDRESS, repo.address);
 | 
					        values.put(Schema.RepoTable.Cols.ADDRESS, uri);
 | 
				
			||||||
        values.put(Schema.RepoTable.Cols.NAME, repo.name);
 | 
					        values.put(Schema.RepoTable.Cols.NAME, name);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        RepoProvider.Helper.insert(context, values);
 | 
					        RepoProvider.Helper.insert(context, values);
 | 
				
			||||||
 | 
					        return RepoProvider.Helper.findByAddress(context, uri);
 | 
				
			||||||
        // Need to reload the repo based on address so that it includes the primary key from
 | 
					 | 
				
			||||||
        // the database.
 | 
					 | 
				
			||||||
        return RepoProvider.Helper.findByAddress(context, repo.address);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected RepoUpdater createRepoUpdater(String name, String uri, Context context) {
 | 
					    protected RepoUpdater createRepoUpdater(String name, String uri, Context context) {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user