diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a8e9d7557..bb851b750 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -270,7 +270,12 @@ - + + = 16 && cm.isActiveNetworkMetered()) { - return FLAG_NET_METERED; - } else { - return FLAG_NET_NO_LIMIT; - } - default: - return FLAG_NET_METERED; - } - } - /** * In order to send a {@link Toast} from a {@link IntentService}, we have to do these tricks. */ @@ -371,8 +339,8 @@ 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) { + int netState = ConnectivityMonitorService.getNetworkState(this); + if (netState == ConnectivityMonitorService.FLAG_NET_UNAVAILABLE) { Utils.debugLog(TAG, "No internet, cannot update"); if (manualUpdate) { sendNoInternetToast(); @@ -380,10 +348,10 @@ public class UpdateService extends IntentService { return; } + final Preferences fdroidPrefs = Preferences.get(); if (manualUpdate || forcedUpdate) { Utils.debugLog(TAG, "manually requested or forced update"); - } else if (!verifyIsTimeForScheduledRun() - || (netState == FLAG_NET_METERED && Preferences.get().isUpdateOnlyOnUnmeteredNetworks())) { + } else if (!verifyIsTimeForScheduledRun() || !fdroidPrefs.isBackgroundDownloadAllowed()) { Utils.debugLog(TAG, "don't run update"); return; } @@ -403,7 +371,6 @@ public class UpdateService extends IntentService { ArrayList repoErrors = new ArrayList<>(); boolean changes = false; boolean singleRepoUpdate = !TextUtils.isEmpty(address); - final Preferences fdroidPrefs = Preferences.get(); for (final Repo repo : repos) { if (!repo.inuse) { continue; @@ -442,7 +409,7 @@ public class UpdateService extends IntentService { } // now that downloading the index is done, start downloading updates - if (changes && fdroidPrefs.isAutoDownloadEnabled()) { + if (changes && fdroidPrefs.isAutoDownloadEnabled() && fdroidPrefs.isBackgroundDownloadAllowed()) { autoDownloadUpdates(this); } } diff --git a/app/src/main/java/org/fdroid/fdroid/Utils.java b/app/src/main/java/org/fdroid/fdroid/Utils.java index 9ca43a347..703b169d7 100644 --- a/app/src/main/java/org/fdroid/fdroid/Utils.java +++ b/app/src/main/java/org/fdroid/fdroid/Utils.java @@ -406,6 +406,12 @@ public final class Utils { return new Locale(languageTag); } + /** + * Since there have been vulnerabilities in EXIF processing in Android, this + * disables all use of EXIF. + * + * @see CVE-2016-3862 + */ public static DisplayImageOptions.Builder getDefaultDisplayImageOptionsBuilder() { if (defaultDisplayImageOptionsBuilder == null) { defaultDisplayImageOptionsBuilder = new DisplayImageOptions.Builder() diff --git a/app/src/main/java/org/fdroid/fdroid/installer/Installer.java b/app/src/main/java/org/fdroid/fdroid/installer/Installer.java index c60d53883..bb0735b4b 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/Installer.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/Installer.java @@ -106,19 +106,23 @@ public abstract class Installer { return intent; } + /** + * Return if this installation process has any new permissions that the user + * should be aware of. Starting in {@code android-23}, all new permissions + * are requested when they are used, and the permissions prompt at time of + * install is not used. All permissions in a new install are considered new. + * + * @return the number of new permissions + */ private int newPermissionCount() { boolean supportsRuntimePermissions = apk.targetSdkVersion >= 23; if (supportsRuntimePermissions) { return 0; } - AppDiff appDiff = new AppDiff(context.getPackageManager(), apk); - if (appDiff.pkgInfo == null) { - // could not get diff because we couldn't parse the package - throw new RuntimeException("cannot parse!"); - } - AppSecurityPermissions perms = new AppSecurityPermissions(context, appDiff.pkgInfo); - if (appDiff.installedAppInfo != null) { + AppDiff appDiff = new AppDiff(context, apk); + AppSecurityPermissions perms = new AppSecurityPermissions(context, appDiff.apkPackageInfo); + if (appDiff.installedApplicationInfo != null) { // update to an existing app return perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW); } diff --git a/app/src/main/java/org/fdroid/fdroid/net/ConnectivityMonitorService.java b/app/src/main/java/org/fdroid/fdroid/net/ConnectivityMonitorService.java new file mode 100644 index 000000000..13df8ee6f --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/net/ConnectivityMonitorService.java @@ -0,0 +1,86 @@ +package org.fdroid.fdroid.net; + +import android.app.IntentService; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Build; +import com.nostra13.universalimageloader.core.ImageLoader; +import org.fdroid.fdroid.FDroidApp; +import org.fdroid.fdroid.Preferences; + +/** + * An {@link IntentService} subclass for tracking whether there is metered or + * unmetered internet available, based on + * {@link android.net.ConnectivityManager#CONNECTIVITY_ACTION} + */ +public class ConnectivityMonitorService extends IntentService { + public static final String TAG = "ConnectivityMonitorServ"; + + public static final int FLAG_NET_UNAVAILABLE = 0; + public static final int FLAG_NET_METERED = 1; + public static final int FLAG_NET_NO_LIMIT = 2; + + private static final String ACTION_START = "org.fdroid.fdroid.net.action.CONNECTIVITY_MONITOR"; + + private static final BroadcastReceiver CONNECTIVITY_RECEIVER = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + start(context); + } + }; + + public ConnectivityMonitorService() { + super("ConnectivityMonitorService"); + } + + public static void registerAndStart(Context context) { + context.registerReceiver(CONNECTIVITY_RECEIVER, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + } + + public static void start(Context context) { + Intent intent = new Intent(context, ConnectivityMonitorService.class); + intent.setAction(ACTION_START); + context.startService(intent); + } + + /** + * 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. + */ + public static int getNetworkState(Context context) { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(CONNECTIVITY_SERVICE); + if (cm == null) { + return FLAG_NET_UNAVAILABLE; + } + NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + if (activeNetwork == null || !activeNetwork.isConnected()) { + return FLAG_NET_UNAVAILABLE; + } + + 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; + } + } + + @Override + protected void onHandleIntent(Intent intent) { + if (intent != null && ACTION_START.equals(intent.getAction())) { + FDroidApp.networkState = getNetworkState(this); + ImageLoader.getInstance().denyNetworkDownloads(!Preferences.get().isBackgroundDownloadAllowed()); + } + } +} diff --git a/app/src/main/java/org/fdroid/fdroid/privileged/views/AppDiff.java b/app/src/main/java/org/fdroid/fdroid/privileged/views/AppDiff.java index 036a78533..5e080e69f 100644 --- a/app/src/main/java/org/fdroid/fdroid/privileged/views/AppDiff.java +++ b/app/src/main/java/org/fdroid/fdroid/privileged/views/AppDiff.java @@ -18,55 +18,51 @@ package org.fdroid.fdroid.privileged.views; +import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; - import org.fdroid.fdroid.data.Apk; +/** + * Represents the permissions difference between the installed APK, and the + * update APK represented by {@link Apk}. + */ public class AppDiff { - private final PackageManager pm; - public final PackageInfo pkgInfo; - public ApplicationInfo installedAppInfo; + public final PackageInfo apkPackageInfo; + public final ApplicationInfo installedApplicationInfo; - /** - * Constructor based on F-Droids Apk object - */ - public AppDiff(PackageManager pm, Apk apk) { - this.pm = pm; + public AppDiff(Context context, Apk apk) { + PackageManager pm = context.getPackageManager(); + apkPackageInfo = new PackageInfo(); + apkPackageInfo.packageName = apk.packageName; + apkPackageInfo.applicationInfo = new ApplicationInfo(); + apkPackageInfo.requestedPermissions = apk.requestedPermissions; - pkgInfo = new PackageInfo(); - pkgInfo.packageName = apk.packageName; - pkgInfo.applicationInfo = new ApplicationInfo(); - pkgInfo.requestedPermissions = apk.requestedPermissions; - - init(); - } - - private void init() { - String pkgName = pkgInfo.packageName; + String packageName = apkPackageInfo.packageName; // Check if there is already a package on the device with this name // but it has been renamed to something else. - final String[] oldName = pm.canonicalToCurrentPackageNames(new String[]{pkgName}); + final String[] oldName = pm.canonicalToCurrentPackageNames(new String[]{packageName}); if (oldName != null && oldName.length > 0 && oldName[0] != null) { - pkgName = oldName[0]; - pkgInfo.packageName = pkgName; - pkgInfo.applicationInfo.packageName = pkgName; + packageName = oldName[0]; + apkPackageInfo.packageName = packageName; + apkPackageInfo.applicationInfo.packageName = packageName; } // Check if package is already installed + ApplicationInfo applicationInfo; try { // This is a little convoluted because we want to get all uninstalled // apps, but this may include apps with just data, and if it is just // data we still want to count it as "installed". //noinspection WrongConstant (lint is actually wrong here!) - installedAppInfo = pm.getApplicationInfo(pkgName, - PackageManager.GET_UNINSTALLED_PACKAGES); - if ((installedAppInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { - installedAppInfo = null; + applicationInfo = pm.getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES); + if ((applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { + applicationInfo = null; } } catch (PackageManager.NameNotFoundException e) { - installedAppInfo = null; + applicationInfo = null; } + installedApplicationInfo = applicationInfo; } } diff --git a/app/src/main/java/org/fdroid/fdroid/privileged/views/InstallConfirmActivity.java b/app/src/main/java/org/fdroid/fdroid/privileged/views/InstallConfirmActivity.java index f9f9791b8..fb450594f 100644 --- a/app/src/main/java/org/fdroid/fdroid/privileged/views/InstallConfirmActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/privileged/views/InstallConfirmActivity.java @@ -92,9 +92,9 @@ public class InstallConfirmActivity extends FragmentActivity implements OnCancel scrollView = null; okCanInstall = false; int msg = 0; - AppSecurityPermissions perms = new AppSecurityPermissions(this, appDiff.pkgInfo); - if (appDiff.installedAppInfo != null) { - msg = (appDiff.installedAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 + AppSecurityPermissions perms = new AppSecurityPermissions(this, appDiff.apkPackageInfo); + if (appDiff.installedApplicationInfo != null) { + msg = (appDiff.installedApplicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ? R.string.install_confirm_update_system : R.string.install_confirm_update; scrollView = new CaffeinatedScrollView(this); @@ -131,10 +131,10 @@ public class InstallConfirmActivity extends FragmentActivity implements OnCancel } if (!permVisible) { - if (appDiff.installedAppInfo != null) { + if (appDiff.installedApplicationInfo != null) { // This is an update to an application, but there are no // permissions at all. - msg = (appDiff.installedAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 + msg = (appDiff.installedApplicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ? R.string.install_confirm_update_system_no_perms : R.string.install_confirm_update_no_perms; } else { @@ -182,7 +182,7 @@ public class InstallConfirmActivity extends FragmentActivity implements OnCancel app = AppProvider.Helper.findSpecificApp(getContentResolver(), apk.packageName, apk.repoId, Schema.AppMetadataTable.Cols.ALL); - appDiff = new AppDiff(getPackageManager(), apk); + appDiff = new AppDiff(this, apk); setContentView(R.layout.install_start); diff --git a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java index 110a622ba..12ffd19bc 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java @@ -789,8 +789,8 @@ public class AppDetailsRecyclerViewAdapter headerView.setText(R.string.permissions); updateExpandableItem(false); contentView.removeAllViews(); - AppDiff appDiff = new AppDiff(context.getPackageManager(), versions.get(0)); - AppSecurityPermissions perms = new AppSecurityPermissions(context, appDiff.pkgInfo); + AppDiff appDiff = new AppDiff(context, versions.get(0)); + AppSecurityPermissions perms = new AppSecurityPermissions(context, appDiff.apkPackageInfo); contentView.addView(perms.getPermissionsView(AppSecurityPermissions.WHICH_ALL)); } diff --git a/app/src/main/java/org/fdroid/fdroid/views/ScreenShotsActivity.java b/app/src/main/java/org/fdroid/fdroid/views/ScreenShotsActivity.java index bc88526df..4e930ce06 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/ScreenShotsActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/ScreenShotsActivity.java @@ -18,6 +18,7 @@ import android.widget.ImageView; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; import org.fdroid.fdroid.FDroidApp; +import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.App; @@ -36,6 +37,8 @@ public class ScreenShotsActivity extends AppCompatActivity { private static final String EXTRA_PACKAGE_NAME = "EXTRA_PACKAGE_NAME"; private static final String EXTRA_START_POSITION = "EXTRA_START_POSITION"; + private static final ImageLoader IMAGE_LOADER = ImageLoader.getInstance(); + public static Intent getStartIntent(Context context, String packageName, int startPosition) { Intent intent = new Intent(context, ScreenShotsActivity.class); intent.putExtra(EXTRA_PACKAGE_NAME, packageName); @@ -68,6 +71,18 @@ public class ScreenShotsActivity extends AppCompatActivity { } } + @Override + protected void onResume() { + super.onResume(); + IMAGE_LOADER.denyNetworkDownloads(false); + } + + @Override + protected void onPause() { + super.onPause(); + IMAGE_LOADER.denyNetworkDownloads(!Preferences.get().isBackgroundDownloadAllowed()); + } + private static class ScreenShotPagerAdapter extends FragmentStatePagerAdapter { private final String[] screenshots; diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 744bd3139..4362f1ef5 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -568,4 +568,12 @@ Inclue sas aplicatziones chi impreant s\'ischermu tàtile Ammustra sas aplicatziones chi tenent bisòngiu de s\'ischermu tàtile in cale si siat dispositivu + Annanghe un\'ispigru + %1$s est giai impostadu, custu at a annànghere informatziones de crae noas. + %1$s est giai impostadu, cunfirma chi lu cheres torrare a abilitare. + %1$s est giai impostadu e abilitadu. + In antis iscantzella %1$s pro annànghere custu cun una crae in cunflitu. + Custa est una còpia de %1$s, lu cheres annànghere comente isprigu? + Isprigos ufitziales + Isprigos impreadores