diff --git a/app/src/main/java/org/fdroid/fdroid/AppDetails.java b/app/src/main/java/org/fdroid/fdroid/AppDetails.java
index 524b35a09..1def0e6ed 100644
--- a/app/src/main/java/org/fdroid/fdroid/AppDetails.java
+++ b/app/src/main/java/org/fdroid/fdroid/AppDetails.java
@@ -1525,8 +1525,10 @@ public class AppDetails extends AppCompatActivity {
public void startProgress(boolean allowCancel) {
cancelButton.setVisibility(allowCancel ? View.VISIBLE : View.GONE);
- showIndeterminateProgress(getString(R.string.download_pending));
- updateViews();
+ if (isAdded()) {
+ showIndeterminateProgress(getString(R.string.download_pending));
+ updateViews();
+ }
}
private void showIndeterminateProgress(String message) {
diff --git a/app/src/main/java/org/fdroid/fdroid/Preferences.java b/app/src/main/java/org/fdroid/fdroid/Preferences.java
index 7a5b91bed..cfe402d47 100644
--- a/app/src/main/java/org/fdroid/fdroid/Preferences.java
+++ b/app/src/main/java/org/fdroid/fdroid/Preferences.java
@@ -231,7 +231,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
return preferences.getBoolean(PREF_AUTO_DOWNLOAD_INSTALL_UPDATES, false);
}
- public boolean isUpdateOnlyOnWifi() {
+ public boolean isUpdateOnlyOnUnmeteredNetworks() {
return preferences.getBoolean(PREF_UPD_WIFI_ONLY, false);
}
diff --git a/app/src/main/java/org/fdroid/fdroid/UpdateService.java b/app/src/main/java/org/fdroid/fdroid/UpdateService.java
index 143f67d19..fbf780caa 100644
--- a/app/src/main/java/org/fdroid/fdroid/UpdateService.java
+++ b/app/src/main/java/org/fdroid/fdroid/UpdateService.java
@@ -31,6 +31,8 @@ import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
import android.os.Process;
import android.os.SystemClock;
import android.preference.PreferenceManager;
@@ -79,6 +81,12 @@ public class UpdateService extends IntentService {
private static final int NOTIFY_ID_UPDATING = 0;
private static final int NOTIFY_ID_UPDATES_AVAILABLE = 1;
+ private static final int FLAG_NET_UNAVAILABLE = 0;
+ private static final int FLAG_NET_METERED = 1;
+ private static final int FLAG_NET_NO_LIMIT = 2;
+
+ private static Handler toastHandler;
+
private NotificationManager notificationManager;
private NotificationCompat.Builder notificationBuilder;
@@ -279,26 +287,50 @@ public class UpdateService extends IntentService {
return false;
}
- return isNetworkAvailableForUpdate(this);
+ return true;
}
/**
- * If we are to update the repos only on wifi, make sure that connection is active
+ * Gets the state of internet availability, whether there is no connection at all,
+ * whether the connection has no usage limit (like most WiFi), or whether this is
+ * a metered connection like most cellular plans or hotspot WiFi connections.
*/
- private static boolean isNetworkAvailableForUpdate(Context context) {
+ private static int getNetworkState(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- // this could be cellular or wifi
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
- if (activeNetwork == null) {
- return false;
+ if (activeNetwork == null || !activeNetwork.isConnected()) {
+ return FLAG_NET_UNAVAILABLE;
}
- if (activeNetwork.getType() != ConnectivityManager.TYPE_WIFI && Preferences.get().isUpdateOnlyOnWifi()) {
- Log.i(TAG, "Skipping update - wifi not available");
- return false;
+ int networkType = activeNetwork.getType();
+ switch (networkType) {
+ case ConnectivityManager.TYPE_ETHERNET:
+ case ConnectivityManager.TYPE_WIFI:
+ if (Build.VERSION.SDK_INT >= 16 && cm.isActiveNetworkMetered()) {
+ return FLAG_NET_METERED;
+ } else {
+ return FLAG_NET_NO_LIMIT;
+ }
+ default:
+ return FLAG_NET_METERED;
}
- return activeNetwork.isConnectedOrConnecting();
+ }
+
+ /**
+ * In order to send a {@link Toast} from a {@link IntentService}, we have to do these tricks.
+ */
+ private void sendNoInternetToast() {
+ if (toastHandler == null) {
+ toastHandler = new Handler(Looper.getMainLooper());
+ }
+ toastHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(getApplicationContext(),
+ R.string.warning_no_internet, Toast.LENGTH_SHORT).show();
+ }
+ });
}
@Override
@@ -315,9 +347,20 @@ public class UpdateService extends IntentService {
try {
// See if it's time to actually do anything yet...
+ int netState = getNetworkState(this);
+ if (netState == FLAG_NET_UNAVAILABLE) {
+ Utils.debugLog(TAG, "No internet, cannot update");
+ if (manualUpdate) {
+ sendNoInternetToast();
+ }
+ return;
+ }
+
if (manualUpdate) {
- Utils.debugLog(TAG, "Unscheduled (manually requested) update");
- } else if (!verifyIsTimeForScheduledRun()) {
+ Utils.debugLog(TAG, "manually requested update");
+ } else if (!verifyIsTimeForScheduledRun()
+ || (netState == FLAG_NET_METERED && Preferences.get().isUpdateOnlyOnUnmeteredNetworks())) {
+ Utils.debugLog(TAG, "don't run update");
return;
}
diff --git a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java
index 74aeb18c4..34616230a 100644
--- a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java
+++ b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java
@@ -11,11 +11,13 @@ import android.net.Uri;
import android.os.Process;
import android.support.annotation.Nullable;
+import org.acra.ACRA;
import org.fdroid.fdroid.Hasher;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Schema.InstalledAppTable;
import java.io.File;
+import java.io.FilenameFilter;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map;
@@ -154,24 +156,49 @@ public class InstalledAppProviderService extends IntentService {
@Override
protected void onHandleIntent(Intent intent) {
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
- if (intent != null) {
- String packageName = intent.getData().getSchemeSpecificPart();
- final String action = intent.getAction();
- if (ACTION_INSERT.equals(action)) {
- PackageInfo packageInfo = getPackageInfo(intent, packageName);
- if (packageInfo != null) {
- File apk = new File(packageInfo.applicationInfo.publicSourceDir);
- if (apk.exists() && apk.canRead()) {
+ if (intent == null) {
+ return;
+ }
+
+ String packageName = intent.getData().getSchemeSpecificPart();
+ final String action = intent.getAction();
+ if (ACTION_INSERT.equals(action)) {
+ PackageInfo packageInfo = getPackageInfo(intent, packageName);
+ if (packageInfo != null) {
+ File apk = new File(packageInfo.applicationInfo.publicSourceDir);
+ if (apk.isDirectory()) {
+ FilenameFilter filter = new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.endsWith(".apk");
+ }
+ };
+ File[] files = apk.listFiles(filter);
+ if (files == null) {
+ String msg = packageName + " sourceDir has no APKs: "
+ + apk.getAbsolutePath();
+ Utils.debugLog(TAG, msg);
+ ACRA.getErrorReporter().handleException(new IllegalArgumentException(msg), false);
+ return;
+ }
+ apk = files[0];
+ }
+ if (apk.exists() && apk.canRead()) {
+ try {
String hashType = "sha256";
String hash = Utils.getBinaryHash(apk, hashType);
insertAppIntoDb(this, packageInfo, hashType, hash);
+ } catch (IllegalArgumentException e) {
+ Utils.debugLog(TAG, e.getMessage());
+ ACRA.getErrorReporter().handleException(e, false);
+ return;
}
}
- } else if (ACTION_DELETE.equals(action)) {
- deleteAppFromDb(this, packageName);
}
- notifyEvents.onNext(null);
+ } else if (ACTION_DELETE.equals(action)) {
+ deleteAppFromDb(this, packageName);
}
+ notifyEvents.onNext(null);
}
/**
diff --git a/app/src/main/java/org/fdroid/fdroid/net/HttpDownloader.java b/app/src/main/java/org/fdroid/fdroid/net/HttpDownloader.java
index 04bf202fa..d792ed2b0 100644
--- a/app/src/main/java/org/fdroid/fdroid/net/HttpDownloader.java
+++ b/app/src/main/java/org/fdroid/fdroid/net/HttpDownloader.java
@@ -3,6 +3,7 @@ package org.fdroid.fdroid.net;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
import org.apache.commons.io.FileUtils;
+import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Utils;
import org.spongycastle.util.encoders.Base64;
@@ -83,6 +84,7 @@ public class HttpDownloader extends Downloader {
// get the file size from the server
HttpURLConnection tmpConn = getConnection();
+ tmpConn.setRequestMethod("HEAD");
int contentLength = -1;
if (tmpConn.getResponseCode() == 200) {
contentLength = tmpConn.getContentLength();
@@ -115,6 +117,8 @@ public class HttpDownloader extends Downloader {
connection = NetCipher.getHttpURLConnection(sourceUrl);
}
+ connection.setRequestProperty("User-Agent", "F-Droid " + BuildConfig.VERSION_NAME);
+
if (username != null && password != null) {
// add authorization header from username / password if set
String authString = username + ":" + password;
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d23295a97..fdb4542a8 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -21,7 +21,7 @@
Automatic update interval
No automatic app list updates
Only on Wi-Fi
- Update app lists automatically only on Wi-Fi
+ Only update automatically on unmetered networks like Wi-Fi
Automatically download updates
Download the update files in the background
Automatically install updates
@@ -201,6 +201,7 @@
All repositories are up to date
All other repos didn\'t create errors.
Error during update: %s
+ Cannot update, are you connected to the internet?
No permissions are used.
Permissions
You don\'t have any available app that can handle %s.