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.