Merge branch 'fix-only-on-wifi' into 'master'

fix "Only on WiFi" to apply to all downloads

See merge request fdroid/fdroidclient!669
This commit is contained in:
Hans-Christoph Steiner 2018-04-06 14:23:27 +00:00
commit 3c14d273b2
12 changed files with 185 additions and 87 deletions

View File

@ -270,7 +270,12 @@
<service
android:name=".DeleteCacheService"
android:exported="false"/>
<service android:name=".net.WifiStateChangeService"/>
<service
android:name=".net.WifiStateChangeService"
android:exported="false"/>
<service
android:name=".net.ConnectivityMonitorService"
android:exported="false"/>
<service android:name=".localrepo.SwapService"/>
<service
android:name=".installer.InstallManagerService"

View File

@ -64,6 +64,7 @@ import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.installer.ApkFileProvider;
import org.fdroid.fdroid.installer.InstallHistoryService;
import org.fdroid.fdroid.net.ConnectivityMonitorService;
import org.fdroid.fdroid.net.ImageLoaderForUIL;
import org.fdroid.fdroid.net.WifiStateChangeService;
import org.fdroid.fdroid.views.hiding.HidingManager;
@ -110,6 +111,8 @@ public class FDroidApp extends Application {
public static volatile String bssid;
public static volatile Repo repo = new Repo();
public static volatile int networkState = ConnectivityMonitorService.FLAG_NET_UNAVAILABLE;
private static volatile String lastWorkingMirror = null;
private static volatile int numTries = Integer.MAX_VALUE;
private static volatile int timeout = 10000;
@ -374,11 +377,10 @@ public class FDroidApp extends Application {
}
});
final Context context = this;
Preferences.get().registerUnstableUpdatesChangeListener(new Preferences.ChangeListener() {
@Override
public void onPreferenceChange() {
AppProvider.Helper.calcSuggestedApks(context);
AppProvider.Helper.calcSuggestedApks(FDroidApp.this);
}
});
@ -434,6 +436,8 @@ public class FDroidApp extends Application {
.build();
ImageLoader.getInstance().init(config);
ConnectivityMonitorService.registerAndStart(this);
FDroidApp.initWifiSettings();
WifiStateChangeService.start(this, null);
// if the HTTPS pref changes, then update all affected things

View File

@ -6,6 +6,7 @@ import android.os.Build;
import android.preference.PreferenceManager;
import android.util.Log;
import info.guardianproject.netcipher.NetCipher;
import org.fdroid.fdroid.net.ConnectivityMonitorService;
import java.net.InetSocketAddress;
import java.net.Proxy;
@ -273,8 +274,14 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
return preferences.getBoolean(PREF_AUTO_DOWNLOAD_INSTALL_UPDATES, false);
}
public boolean isUpdateOnlyOnUnmeteredNetworks() {
return preferences.getBoolean(PREF_UPD_WIFI_ONLY, false);
/**
* Do the network conditions and user preferences allow for things to be
* downloaded in the background.
*/
public boolean isBackgroundDownloadAllowed() {
return FDroidApp.networkState == ConnectivityMonitorService.FLAG_NET_NO_LIMIT ||
(FDroidApp.networkState == ConnectivityMonitorService.FLAG_NET_METERED
&& !preferences.getBoolean(PREF_UPD_WIFI_ONLY, false));
}
/**

View File

@ -27,8 +27,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@ -49,6 +47,7 @@ import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.Schema;
import org.fdroid.fdroid.installer.InstallManagerService;
import org.fdroid.fdroid.net.ConnectivityMonitorService;
import org.fdroid.fdroid.views.main.MainActivity;
import java.util.ArrayList;
@ -79,10 +78,6 @@ public class UpdateService extends IntentService {
private static final int NOTIFY_ID_UPDATING = 0;
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;
@ -312,33 +307,6 @@ public class UpdateService extends IntentService {
return true;
}
/**
* 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 int getNetworkState(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
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;
}
}
/**
* 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<CharSequence> 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);
}
}

View File

@ -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 <a href="https://securityaffairs.co/wordpress/51043/mobile-2/android-cve-2016-3862-flaw.html">CVE-2016-3862</a>
*/
public static DisplayImageOptions.Builder getDefaultDisplayImageOptionsBuilder() {
if (defaultDisplayImageOptionsBuilder == null) {
defaultDisplayImageOptionsBuilder = new DisplayImageOptions.Builder()

View File

@ -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);
}

View File

@ -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());
}
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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));
}

View File

@ -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;

View File

@ -568,4 +568,12 @@
<string name="force_touch_apps">Inclue sas aplicatziones chi impreant s\'ischermu tàtile</string>
<string name="force_touch_apps_on">Ammustra sas aplicatziones chi tenent bisòngiu de s\'ischermu tàtile in cale si siat dispositivu</string>
<string name="repo_add_mirror">Annanghe un\'ispigru</string>
<string name="repo_exists_add_fingerprint">%1$s est giai impostadu, custu at a annànghere informatziones de crae noas.</string>
<string name="repo_exists_enable">%1$s est giai impostadu, cunfirma chi lu cheres torrare a abilitare.</string>
<string name="repo_exists_and_enabled">%1$s est giai impostadu e abilitadu.</string>
<string name="repo_delete_to_overwrite">In antis iscantzella %1$s pro annànghere custu cun una crae in cunflitu.</string>
<string name="repo_exists_add_mirror">Custa est una còpia de %1$s, lu cheres annànghere comente isprigu?</string>
<string name="repo_official_mirrors">Isprigos ufitziales</string>
<string name="repo_user_mirrors">Isprigos impreadores</string>
</resources>