diff --git a/.classpath b/.classpath
index fadbb9f4b..b6c82120d 100644
--- a/.classpath
+++ b/.classpath
@@ -4,7 +4,11 @@
-
+
+
+
+
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9ea7c7e0d..3943d2260 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,8 @@
* find local repos on the same network using Bonjour/mDNS
+* use FDroid repos on Tor Hidden Services (.onion addresses)
+
* directly send installed apps to other devices via Bluetooth and Android Beam
(NFC+Bluetooth), also compatible with Samsung/HTC S-Beam
diff --git a/src/org/fdroid/fdroid/FDroidApp.java b/src/org/fdroid/fdroid/FDroidApp.java
index e5d8c13fe..16d936fce 100644
--- a/src/org/fdroid/fdroid/FDroidApp.java
+++ b/src/org/fdroid/fdroid/FDroidApp.java
@@ -41,11 +41,13 @@ import com.nostra13.universalimageloader.utils.StorageUtils;
import de.duenndns.ssl.MemorizingTrustManager;
+import org.fdroid.fdroid.Preferences.ChangeListener;
import org.fdroid.fdroid.compat.PRNGFixes;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.InstalledAppCacheUpdater;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.localrepo.LocalRepoService;
+import org.fdroid.fdroid.net.IconDownloader;
import org.fdroid.fdroid.net.WifiStateChangeService;
import org.thoughtcrime.ssl.pinning.PinningTrustManager;
import org.thoughtcrime.ssl.pinning.SystemKeyStore;
@@ -151,7 +153,8 @@ public class FDroidApp extends Application {
bluetoothAdapter = getBluetoothAdapter();
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
- .discCache(new LimitedAgeDiscCache(
+ .imageDownloader(new IconDownloader(getApplicationContext()))
+ .diskCache(new LimitedAgeDiscCache(
new File(StorageUtils.getCacheDirectory(getApplicationContext(), true),
"icons"),
null,
@@ -213,6 +216,13 @@ public class FDroidApp extends Application {
if (wifiState == WifiManager.WIFI_STATE_ENABLING
|| wifiState == WifiManager.WIFI_STATE_ENABLED)
startService(new Intent(this, WifiStateChangeService.class));
+ // if the HTTPS pref changes, then update all affected things
+ Preferences.get().registerLocalRepoHttpsListeners(new ChangeListener() {
+ @Override
+ public void onPreferenceChange() {
+ startService(new Intent(FDroidApp.this, WifiStateChangeService.class));
+ }
+ });
}
@TargetApi(18)
@@ -282,17 +292,21 @@ public class FDroidApp extends Application {
public static void startLocalRepoService(Context context) {
if (!localRepoServiceIsBound) {
Context app = context.getApplicationContext();
- app.bindService(new Intent(app, LocalRepoService.class),
- serviceConnection, Context.BIND_AUTO_CREATE);
- localRepoServiceIsBound = true;
+ Intent service = new Intent(app, LocalRepoService.class);
+ localRepoServiceIsBound = app.bindService(service, serviceConnection,
+ Context.BIND_AUTO_CREATE);
+ if (localRepoServiceIsBound)
+ app.startService(service);
}
}
public static void stopLocalRepoService(Context context) {
+ Context app = context.getApplicationContext();
if (localRepoServiceIsBound) {
- context.getApplicationContext().unbindService(serviceConnection);
+ app.unbindService(serviceConnection);
localRepoServiceIsBound = false;
}
+ app.stopService(new Intent(app, LocalRepoService.class));
}
public static void restartLocalRepoService() {
diff --git a/src/org/fdroid/fdroid/localrepo/LocalRepoService.java b/src/org/fdroid/fdroid/localrepo/LocalRepoService.java
index fc182352d..d05826b83 100644
--- a/src/org/fdroid/fdroid/localrepo/LocalRepoService.java
+++ b/src/org/fdroid/fdroid/localrepo/LocalRepoService.java
@@ -216,25 +216,31 @@ public class LocalRepoService extends Service {
}
private void registerMDNSService() {
- String repoName = Preferences.get().getLocalRepoName();
- final HashMap values = new HashMap();
- values.put("path", "/fdroid/repo");
- values.put("name", repoName);
- values.put("fingerprint", FDroidApp.repo.fingerprint);
- String type;
- if (Preferences.get().isLocalRepoHttpsEnabled()) {
- values.put("type", "fdroidrepos");
- type = "_https._tcp.local.";
- } else {
- values.put("type", "fdroidrepo");
- type = "_http._tcp.local.";
- }
- pairService = ServiceInfo.create(type, repoName, FDroidApp.port, 0, 0, values);
new Thread(new Runnable() {
-
@Override
public void run() {
+ /*
+ * a ServiceInfo can only be registered with a single instance
+ * of JmDNS, and there is only ever a single LocalHTTPD port to
+ * advertise anyway.
+ */
+ if (pairService != null || jmdns != null)
+ clearCurrentMDNSService();
+ String repoName = Preferences.get().getLocalRepoName();
+ HashMap values = new HashMap();
+ values.put("path", "/fdroid/repo");
+ values.put("name", repoName);
+ values.put("fingerprint", FDroidApp.repo.fingerprint);
+ String type;
+ if (Preferences.get().isLocalRepoHttpsEnabled()) {
+ values.put("type", "fdroidrepos");
+ type = "_https._tcp.local.";
+ } else {
+ values.put("type", "fdroidrepo");
+ type = "_http._tcp.local.";
+ }
try {
+ pairService = ServiceInfo.create(type, repoName, FDroidApp.port, 0, 0, values);
jmdns = JmDNS.create();
jmdns.registerService(pairService);
} catch (IOException e) {
@@ -249,6 +255,10 @@ public class LocalRepoService extends Service {
Preferences.get().unregisterLocalRepoBonjourListeners(localRepoBonjourChangeListener);
localRepoBonjourChangeListener = null;
}
+ clearCurrentMDNSService();
+ }
+
+ private void clearCurrentMDNSService() {
if (jmdns != null) {
if (pairService != null) {
jmdns.unregisterService(pairService);
diff --git a/src/org/fdroid/fdroid/net/ApkDownloader.java b/src/org/fdroid/fdroid/net/ApkDownloader.java
index baddfd5ef..c8c6bcc15 100644
--- a/src/org/fdroid/fdroid/net/ApkDownloader.java
+++ b/src/org/fdroid/fdroid/net/ApkDownloader.java
@@ -22,6 +22,7 @@ package org.fdroid.fdroid.net;
import android.os.Bundle;
import android.util.Log;
+
import org.fdroid.fdroid.Hasher;
import org.fdroid.fdroid.ProgressListener;
import org.fdroid.fdroid.data.Apk;
@@ -172,8 +173,7 @@ public class ApkDownloader implements AsyncDownloadWrapper.Listener {
Log.d(TAG, "Downloading apk from " + remoteAddress);
try {
-
- Downloader downloader = new HttpDownloader(remoteAddress, localFile);
+ Downloader downloader = DownloaderFactory.create(remoteAddress, localFile);
dlWrapper = new AsyncDownloadWrapper(downloader, this);
dlWrapper.download();
return true;
diff --git a/src/org/fdroid/fdroid/net/Downloader.java b/src/org/fdroid/fdroid/net/Downloader.java
index 8c9f7cf77..593777d52 100644
--- a/src/org/fdroid/fdroid/net/Downloader.java
+++ b/src/org/fdroid/fdroid/net/Downloader.java
@@ -14,6 +14,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
+import java.net.URL;
public abstract class Downloader {
@@ -23,31 +24,32 @@ public abstract class Downloader {
private ProgressListener progressListener = null;
private Bundle eventData = null;
private File outputFile;
+
+ protected URL sourceUrl;
protected String cacheTag = null;
public static final String EVENT_PROGRESS = "downloadProgress";
- public abstract InputStream inputStream() throws IOException;
+ public abstract InputStream getInputStream() throws IOException;
// The context is required for opening the file to write to.
- public Downloader(String destFile, Context ctx)
+ Downloader(String destFile, Context ctx)
throws FileNotFoundException, MalformedURLException {
this(new File(ctx.getFilesDir() + File.separator + destFile));
}
// The context is required for opening the file to write to.
- public Downloader(Context ctx) throws IOException {
+ Downloader(Context ctx) throws IOException {
this(File.createTempFile("dl-", "", ctx.getCacheDir()));
}
- public Downloader(File destFile)
+ Downloader(File destFile)
throws FileNotFoundException, MalformedURLException {
- // http://developer.android.com/guide/topics/data/data-storage.html#InternalCache
outputFile = destFile;
outputStream = new FileOutputStream(outputFile);
}
- public Downloader(OutputStream output)
+ Downloader(OutputStream output)
throws MalformedURLException {
outputStream = output;
outputFile = null;
@@ -118,13 +120,13 @@ public abstract class Downloader {
Log.d(TAG, "Downloading from stream");
InputStream input = null;
try {
- input = inputStream();
+ input = getInputStream();
// Getting the input stream is slow(ish) for HTTP downloads, so we'll check if
// we were interrupted before proceeding to the download.
throwExceptionIfInterrupted();
- copyInputToOutputStream(inputStream());
+ copyInputToOutputStream(getInputStream());
} finally {
Utils.closeQuietly(outputStream);
Utils.closeQuietly(input);
diff --git a/src/org/fdroid/fdroid/net/DownloaderFactory.java b/src/org/fdroid/fdroid/net/DownloaderFactory.java
new file mode 100644
index 000000000..df0ab415b
--- /dev/null
+++ b/src/org/fdroid/fdroid/net/DownloaderFactory.java
@@ -0,0 +1,32 @@
+
+package org.fdroid.fdroid.net;
+
+import android.content.Context;
+
+import java.io.File;
+import java.io.IOException;
+
+public class DownloaderFactory {
+
+ public static Downloader create(String url, Context context)
+ throws IOException {
+ if (isOnionAddress(url)) {
+ return new TorHttpDownloader(url, context);
+ } else {
+ return new HttpDownloader(url, context);
+ }
+ }
+
+ public static Downloader create(String url, File destFile)
+ throws IOException {
+ if (isOnionAddress(url)) {
+ return new TorHttpDownloader(url, destFile);
+ } else {
+ return new HttpDownloader(url, destFile);
+ }
+ }
+
+ private static boolean isOnionAddress(String url) {
+ return url.matches("^[a-zA-Z0-9]+://[^/]+\\.onion/.*");
+ }
+}
diff --git a/src/org/fdroid/fdroid/net/HttpDownloader.java b/src/org/fdroid/fdroid/net/HttpDownloader.java
index 177fa6de1..f1dab1ffc 100644
--- a/src/org/fdroid/fdroid/net/HttpDownloader.java
+++ b/src/org/fdroid/fdroid/net/HttpDownloader.java
@@ -7,33 +7,23 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
-import java.net.UnknownHostException;
import javax.net.ssl.SSLHandshakeException;
public class HttpDownloader extends Downloader {
private static final String TAG = "org.fdroid.fdroid.net.HttpDownloader";
- private static final String HEADER_IF_NONE_MATCH = "If-None-Match";
- private static final String HEADER_FIELD_ETAG = "ETag";
+ protected static final String HEADER_IF_NONE_MATCH = "If-None-Match";
+ protected static final String HEADER_FIELD_ETAG = "ETag";
- private URL sourceUrl;
- private HttpURLConnection connection;
+ protected HttpURLConnection connection;
private int statusCode = -1;
// The context is required for opening the file to write to.
- public HttpDownloader(String source, String destFile, Context ctx)
- throws FileNotFoundException, MalformedURLException {
- super(destFile, ctx);
- sourceUrl = new URL(source);
- }
-
- // The context is required for opening the file to write to.
- public HttpDownloader(String source, File destFile)
+ HttpDownloader(String source, File destFile)
throws FileNotFoundException, MalformedURLException {
super(destFile);
sourceUrl = new URL(source);
@@ -42,20 +32,17 @@ public class HttpDownloader extends Downloader {
/**
* Downloads to a temporary file, which *you must delete yourself when
* you are done*.
- * @see org.fdroid.fdroid.net.HttpDownloader#getFile()
+ * @see org.fdroid.fdroid.net.Downloader#getFile()
*/
- public HttpDownloader(String source, Context ctx) throws IOException {
+ HttpDownloader(String source, Context ctx) throws IOException {
super(ctx);
sourceUrl = new URL(source);
}
- public HttpDownloader(String source, OutputStream output)
- throws MalformedURLException {
- super(output);
- sourceUrl = new URL(source);
- }
-
- public InputStream inputStream() throws IOException {
+ @Override
+ public InputStream getInputStream() throws IOException {
+ setupConnection();
+ // TODO check out BaseImageDownloader.getStreamFromNetwork() for optims
return connection.getInputStream();
}
@@ -68,31 +55,41 @@ public class HttpDownloader extends Downloader {
@Override
public void download() throws IOException, InterruptedException {
try {
- connection = (HttpURLConnection)sourceUrl.openConnection();
-
- if (wantToCheckCache()) {
- setupCacheCheck();
- Log.i(TAG, "Checking cached status of " + sourceUrl);
- statusCode = connection.getResponseCode();
- }
-
- if (isCached()) {
- Log.i(TAG, sourceUrl + " is cached, so not downloading (HTTP " + statusCode + ")");
- } else {
- Log.i(TAG, "Downloading from " + sourceUrl);
- downloadFromStream();
- updateCacheCheck();
- }
+ setupConnection();
+ doDownload();
} catch (SSLHandshakeException e) {
- // TODO this should be handled better, it is not internationalised here.
+ // TODO this should be handled better, it is not internationalised here
throw new IOException(
"A problem occurred while establishing an SSL " +
"connection. If this problem persists, AND you have a " +
"very old device, you could try using http instead of " +
- "https for the repo URL." + Log.getStackTraceString(e) );
+ "https for the repo URL." + Log.getStackTraceString(e));
}
}
+ protected void setupConnection() throws IOException {
+ if (connection != null)
+ return;
+ connection = (HttpURLConnection) sourceUrl.openConnection();
+ }
+
+ protected void doDownload() throws IOException, InterruptedException {
+ if (wantToCheckCache()) {
+ setupCacheCheck();
+ Log.i(TAG, "Checking cached status of " + sourceUrl);
+ statusCode = connection.getResponseCode();
+ }
+
+ if (isCached()) {
+ Log.i(TAG, sourceUrl + " is cached, so not downloading (HTTP " + statusCode + ")");
+ } else {
+ Log.i(TAG, "Downloading from " + sourceUrl);
+ downloadFromStream();
+ updateCacheCheck();
+ }
+ }
+
+ @Override
public boolean isCached() {
return wantToCheckCache() && statusCode == 304;
}
diff --git a/src/org/fdroid/fdroid/net/IconDownloader.java b/src/org/fdroid/fdroid/net/IconDownloader.java
new file mode 100644
index 000000000..a8dedf17a
--- /dev/null
+++ b/src/org/fdroid/fdroid/net/IconDownloader.java
@@ -0,0 +1,32 @@
+
+package org.fdroid.fdroid.net;
+
+import android.content.Context;
+
+import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class IconDownloader extends BaseImageDownloader {
+
+ public IconDownloader(Context context) {
+ super(context);
+ }
+
+ public IconDownloader(Context context, int connectTimeout, int readTimeout) {
+ super(context, connectTimeout, readTimeout);
+ }
+
+ @Override
+ public InputStream getStream(String imageUri, Object extra) throws IOException {
+ switch (Scheme.ofUri(imageUri)) {
+ case HTTP:
+ case HTTPS:
+ Downloader downloader = DownloaderFactory.create(imageUri, context);
+ return downloader.getInputStream();
+ default:
+ return super.getStream(imageUri, extra);
+ }
+ }
+}
diff --git a/src/org/fdroid/fdroid/net/TorHttpDownloader.java b/src/org/fdroid/fdroid/net/TorHttpDownloader.java
new file mode 100644
index 000000000..824ff6433
--- /dev/null
+++ b/src/org/fdroid/fdroid/net/TorHttpDownloader.java
@@ -0,0 +1,32 @@
+
+package org.fdroid.fdroid.net;
+
+import android.content.Context;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.Proxy;
+import java.net.SocketAddress;
+
+public class TorHttpDownloader extends HttpDownloader {
+
+ TorHttpDownloader(String url, Context ctx) throws IOException {
+ super(url, ctx);
+ }
+
+ TorHttpDownloader(String url, File destFile)
+ throws FileNotFoundException, MalformedURLException {
+ super(url, destFile);
+ }
+
+ @Override
+ protected void setupConnection() throws IOException {
+ SocketAddress sa = new InetSocketAddress("127.0.0.1", 8118);
+ Proxy tor = new Proxy(Proxy.Type.HTTP, sa);
+ connection = (HttpURLConnection) sourceUrl.openConnection(tor);
+ }
+}
diff --git a/src/org/fdroid/fdroid/net/WifiStateChangeService.java b/src/org/fdroid/fdroid/net/WifiStateChangeService.java
index c24271ff9..6b4fda481 100644
--- a/src/org/fdroid/fdroid/net/WifiStateChangeService.java
+++ b/src/org/fdroid/fdroid/net/WifiStateChangeService.java
@@ -23,9 +23,14 @@ import java.util.Locale;
public class WifiStateChangeService extends Service {
public static final String BROADCAST = "org.fdroid.fdroid.action.WIFI_CHANGE";
+ private static WaitForWifiAsyncTask asyncTask;
+
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- new WaitForWifiAsyncTask().execute();
+ if (asyncTask != null)
+ asyncTask.cancel(true);
+ asyncTask = new WaitForWifiAsyncTask();
+ asyncTask.execute();
return START_NOT_STICKY;
}
@@ -38,15 +43,21 @@ public class WifiStateChangeService extends Service {
wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
try {
while (!wifiManager.isWifiEnabled()) {
+ if (isCancelled())
+ return null;
Log.i(TAG, "waiting for the wifi to be enabled...");
- Thread.sleep(3000);
+ Thread.sleep(1000);
}
int ipAddress = wifiManager.getConnectionInfo().getIpAddress();
while (ipAddress == 0) {
+ if (isCancelled())
+ return null;
Log.i(TAG, "waiting for an IP address...");
- Thread.sleep(3000);
+ Thread.sleep(1000);
ipAddress = wifiManager.getConnectionInfo().getIpAddress();
}
+ if (isCancelled())
+ return null;
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
ipAddress = wifiInfo.getIpAddress();
FDroidApp.ipAddressString = String.format(Locale.ENGLISH, "%d.%d.%d.%d",
@@ -67,6 +78,9 @@ public class WifiStateChangeService extends Service {
FDroidApp.repo.address = String.format(Locale.ENGLISH, "%s://%s:%d/fdroid/repo",
scheme, FDroidApp.ipAddressString, FDroidApp.port);
+ if (isCancelled())
+ return null;
+
Context context = WifiStateChangeService.this.getApplicationContext();
LocalRepoKeyStore localRepoKeyStore = LocalRepoKeyStore.get(context);
Certificate localCert = localRepoKeyStore.getCertificate();
@@ -75,6 +89,9 @@ public class WifiStateChangeService extends Service {
lrm.setUriString(FDroidApp.repo.address);
lrm.writeIndexPage(Utils.getSharingUri(context, FDroidApp.repo).toString());
+ if (isCancelled())
+ return null;
+
/*
* Once the IP address is known we need to generate a self
* signed certificate to use for HTTPS that has a CN field set
diff --git a/src/org/fdroid/fdroid/updater/RepoUpdater.java b/src/org/fdroid/fdroid/updater/RepoUpdater.java
index eab4291d7..7cb788ed3 100644
--- a/src/org/fdroid/fdroid/updater/RepoUpdater.java
+++ b/src/org/fdroid/fdroid/updater/RepoUpdater.java
@@ -13,7 +13,7 @@ import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.net.Downloader;
-import org.fdroid.fdroid.net.HttpDownloader;
+import org.fdroid.fdroid.net.DownloaderFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
@@ -89,7 +89,7 @@ abstract public class RepoUpdater {
protected Downloader downloadIndex() throws UpdateException {
Downloader downloader = null;
try {
- downloader = new HttpDownloader(getIndexAddress(), context);
+ downloader = DownloaderFactory.create(getIndexAddress(), context);
downloader.setCacheTag(repo.lastetag);
if (progressListener != null) { // interactive session, show progress