diff --git a/app/src/basic/res/xml/preferences.xml b/app/src/basic/res/xml/preferences.xml index 0077b831a..d5a48b4d5 100644 --- a/app/src/basic/res/xml/preferences.xml +++ b/app/src/basic/res/xml/preferences.xml @@ -76,11 +76,13 @@ android:title="@string/show_incompat_versions" android:defaultValue="false" android:key="incompatibleVersions"/> - - + diff --git a/app/src/main/java/org/fdroid/fdroid/Preferences.java b/app/src/main/java/org/fdroid/fdroid/Preferences.java index 25ca46f27..5bf1fd0fe 100644 --- a/app/src/main/java/org/fdroid/fdroid/Preferences.java +++ b/app/src/main/java/org/fdroid/fdroid/Preferences.java @@ -80,6 +80,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh PrivilegedInstaller.isExtensionInstalledCorrectly(context) != PrivilegedInstaller.IS_EXTENSION_INSTALLED_YES); } + editor.apply(); } @@ -91,7 +92,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh public static final String PREF_THEME = "theme"; public static final String PREF_USE_PURE_BLACK_DARK_THEME = "usePureBlackDarkTheme"; public static final String PREF_SHOW_INCOMPAT_VERSIONS = "incompatibleVersions"; - public static final String PREF_SHOW_ANTI_FEATURE_APPS = "showAntiFeatureApps"; + public static final String PREF_SHOW_ANTI_FEATURES = "showAntiFeatures"; public static final String PREF_FORCE_TOUCH_APPS = "ignoreTouchscreen"; public static final String PREF_PROMPT_TO_SEND_CRASH_REPORTS = "promptToSendCrashReports"; public static final String PREF_KEEP_CACHE_TIME = "keepCacheFor"; @@ -170,7 +171,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh DateUtils.HOUR_IN_MILLIS, }; - private boolean showAppsWithAntiFeatures; + private Set showAppsWithAntiFeatures; private final Map initialized = new HashMap<>(); @@ -585,18 +586,15 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh * This is cached as it is called several times inside app list adapters. * Providing it here means the shared preferences file only needs to be * read once, and we will keep our copy up to date by listening to changes - * in PREF_SHOW_ANTI_FEATURE_APPS. + * in PREF_SHOW_ANTI_FEATURES. */ - public boolean showAppsWithAntiFeatures() { - // migrate old preference to new key - if (isInitialized("hideAntiFeatureApps")) { - boolean oldPreference = preferences.getBoolean("hideAntiFeatureApps", false); - preferences.edit().putBoolean(PREF_SHOW_ANTI_FEATURE_APPS, !oldPreference).apply(); - } - if (!isInitialized(PREF_SHOW_ANTI_FEATURE_APPS)) { - initialize(PREF_SHOW_ANTI_FEATURE_APPS); - showAppsWithAntiFeatures = preferences.getBoolean(PREF_SHOW_ANTI_FEATURE_APPS, IGNORED_B); + public Set showAppsWithAntiFeatures() { + if (!isInitialized(PREF_SHOW_ANTI_FEATURES)) { + initialize(PREF_SHOW_ANTI_FEATURES); + showAppsWithAntiFeatures = preferences.getStringSet( + PREF_SHOW_ANTI_FEATURES, null); } + return showAppsWithAntiFeatures; } @@ -622,7 +620,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh uninitialize(key); switch (key) { - case PREF_SHOW_ANTI_FEATURE_APPS: + case PREF_SHOW_ANTI_FEATURES: for (ChangeListener listener : showAppsRequiringAntiFeaturesListeners) { listener.onPreferenceChange(); } diff --git a/app/src/main/java/org/fdroid/fdroid/data/App.java b/app/src/main/java/org/fdroid/fdroid/data/App.java index 02693a052..cbbf3df88 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/App.java +++ b/app/src/main/java/org/fdroid/fdroid/data/App.java @@ -1139,12 +1139,22 @@ public class App extends ValueObject implements Comparable, Parcelable { /** * @return if the given app should be filtered out based on the - * {@link Preferences#PREF_SHOW_ANTI_FEATURE_APPS Show Anti-Features Setting} + * {@link Preferences#PREF_SHOW_ANTI_FEATURES Show Anti-Features Setting} */ public boolean isDisabledByAntiFeatures() { - return this.antiFeatures != null - && this.antiFeatures.length > 0 - && !Preferences.get().showAppsWithAntiFeatures(); + if (this.antiFeatures == null) { + return false; + } + + Set shownAntiFeatures = Preferences.get().showAppsWithAntiFeatures(); + + for (String antiFeature : this.antiFeatures) { + if (!shownAntiFeatures.contains(antiFeature)) { + return true; + } + } + + return false; } @Nullable diff --git a/app/src/main/java/org/fdroid/fdroid/views/PreferencesFragment.java b/app/src/main/java/org/fdroid/fdroid/views/PreferencesFragment.java index 41a4e5cbb..9194fb610 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/PreferencesFragment.java +++ b/app/src/main/java/org/fdroid/fdroid/views/PreferencesFragment.java @@ -79,7 +79,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat Preferences.PREF_OVER_DATA, Preferences.PREF_UPDATE_INTERVAL, Preferences.PREF_UPDATE_NOTIFICATION_ENABLED, - Preferences.PREF_SHOW_ANTI_FEATURE_APPS, + Preferences.PREF_SHOW_ANTI_FEATURES, Preferences.PREF_SHOW_INCOMPAT_VERSIONS, Preferences.PREF_THEME, Preferences.PREF_USE_PURE_BLACK_DARK_THEME, @@ -306,7 +306,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat checkSummary(key, R.string.show_incompat_versions_on); break; - case Preferences.PREF_SHOW_ANTI_FEATURE_APPS: + case Preferences.PREF_SHOW_ANTI_FEATURES: checkSummary(key, R.string.show_anti_feature_apps_on); break; diff --git a/app/src/main/java/org/fdroid/fdroid/views/appdetails/AntiFeaturesListingView.java b/app/src/main/java/org/fdroid/fdroid/views/appdetails/AntiFeaturesListingView.java index 8bf16473d..3caddc489 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/appdetails/AntiFeaturesListingView.java +++ b/app/src/main/java/org/fdroid/fdroid/views/appdetails/AntiFeaturesListingView.java @@ -38,10 +38,103 @@ import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import java.util.HashMap; + public class AntiFeaturesListingView extends RecyclerView { + static HashMap antiFeatureDescriptions; + static HashMap antiFeatureIcons; + public AntiFeaturesListingView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); + + if (antiFeatureDescriptions == null) { + antiFeatureDescriptions = new HashMap<>(); + antiFeatureDescriptions.put( + context.getString(R.string.antiads_key), + context.getString(R.string.antiadslist) + ); + antiFeatureDescriptions.put( + context.getString(R.string.antitrack_key), + context.getString(R.string.antitracklist) + ); + antiFeatureDescriptions.put( + context.getString(R.string.antinonfreenet_key), + context.getString(R.string.antinonfreenetlist) + ); + antiFeatureDescriptions.put( + context.getString(R.string.antinonfreead_key), + context.getString(R.string.antinonfreeadlist) + ); + antiFeatureDescriptions.put( + context.getString(R.string.antinonfreedep_key), + context.getString(R.string.antinonfreedeplist) + ); + antiFeatureDescriptions.put( + context.getString(R.string.antiupstreamnonfree_key), + context.getString(R.string.antiupstreamnonfreelist) + ); + antiFeatureDescriptions.put( + context.getString(R.string.antinonfreeassets_key), + context.getString(R.string.antinonfreeassetslist) + ); + antiFeatureDescriptions.put( + context.getString(R.string.antidisabledalgorithm_key), + context.getString(R.string.antidisabledalgorithmlist) + ); + antiFeatureDescriptions.put( + context.getString(R.string.antiknownvuln_key), + context.getString(R.string.antiknownvulnlist) + ); + antiFeatureDescriptions.put( + context.getString(R.string.antinosource_key), + context.getString(R.string.antinosourcesince) + ); + } + + if (antiFeatureIcons == null) { + antiFeatureIcons = new HashMap<>(); + antiFeatureIcons.put( + context.getString(R.string.antiads_key), + R.drawable.ic_antifeature_ads + ); + antiFeatureIcons.put( + context.getString(R.string.antitrack_key), + R.drawable.ic_antifeature_tracking + ); + antiFeatureIcons.put( + context.getString(R.string.antinonfreenet_key), + R.drawable.ic_antifeature_nonfreenet + ); + antiFeatureIcons.put( + context.getString(R.string.antinonfreead_key), + R.drawable.ic_antifeature_nonfreeadd + ); + antiFeatureIcons.put( + context.getString(R.string.antinonfreedep_key), + R.drawable.ic_antifeature_nonfreedep + ); + antiFeatureIcons.put( + context.getString(R.string.antiupstreamnonfree_key), + R.drawable.ic_antifeature_upstreamnonfree + ); + antiFeatureIcons.put( + context.getString(R.string.antinonfreeassets_key), + R.drawable.ic_antifeature_nonfreeassets + ); + antiFeatureIcons.put( + context.getString(R.string.antidisabledalgorithm_key), + R.drawable.ic_antifeature_disabledalgorithm + ); + antiFeatureIcons.put( + context.getString(R.string.antiknownvuln_key), + R.drawable.ic_antifeature_knownvuln + ); + antiFeatureIcons.put( + context.getString(R.string.antinosource_key), + R.drawable.ic_antifeature_nosourcesince + ); + } } public void setApp(final App app) { @@ -67,7 +160,7 @@ public class AntiFeaturesListingView extends RecyclerView { holder.antiFeatureIcon.setBackgroundDrawable( ContextCompat.getDrawable(getContext(), antiFeatureIcon(antiFeatureName))); holder.antiFeatureText.setText( - getAntiFeatureDescriptionText(holder.antiFeatureText.getContext(), antiFeatureName)); + getAntiFeatureDescriptionText(antiFeatureName)); holder.entireView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -105,57 +198,21 @@ public class AntiFeaturesListingView extends RecyclerView { } - public static String getAntiFeatureDescriptionText(Context context, String antiFeatureName) { - switch (antiFeatureName) { - case "Ads": - return context.getString(R.string.antiadslist); - case "Tracking": - return context.getString(R.string.antitracklist); - case "NonFreeNet": - return context.getString(R.string.antinonfreenetlist); - case "NonFreeAdd": - return context.getString(R.string.antinonfreeadlist); - case "NonFreeDep": - return context.getString(R.string.antinonfreedeplist); - case "UpstreamNonFree": - return context.getString(R.string.antiupstreamnonfreelist); - case "NonFreeAssets": - return context.getString(R.string.antinonfreeassetslist); - case "DisabledAlgorithm": - return context.getString(R.string.antidisabledalgorithmlist); - case "KnownVuln": - return context.getString(R.string.antiknownvulnlist); - case "NoSourceSince": - return context.getString(R.string.antinosourcesince); - default: - return antiFeatureName; + public static String getAntiFeatureDescriptionText(String antiFeatureName) { + String description = antiFeatureDescriptions.get(antiFeatureName); + if (description == null) { + return antiFeatureName; } + + return description; } public static @DrawableRes int antiFeatureIcon(String antiFeatureName) { - switch (antiFeatureName) { - case "Ads": - return R.drawable.ic_antifeature_ads; - case "Tracking": - return R.drawable.ic_antifeature_tracking; - case "NonFreeNet": - return R.drawable.ic_antifeature_nonfreenet; - case "NonFreeAdd": - return R.drawable.ic_antifeature_nonfreeadd; - case "NonFreeDep": - return R.drawable.ic_antifeature_nonfreedep; - case "UpstreamNonFree": - return R.drawable.ic_antifeature_upstreamnonfree; - case "NonFreeAssets": - return R.drawable.ic_antifeature_nonfreeassets; - case "DisabledAlgorithm": - return R.drawable.ic_antifeature_disabledalgorithm; - case "KnownVuln": - return R.drawable.ic_antifeature_knownvuln; - case "NoSourceSince": - return R.drawable.ic_antifeature_nosourcesince; - default: - return R.drawable.ic_cancel; + Integer icon = antiFeatureIcons.get(antiFeatureName); + if (icon == null) { + return R.drawable.ic_cancel; } + + return icon; } } diff --git a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListActivity.java b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListActivity.java index 4b228f2b7..708a324fa 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListActivity.java @@ -46,6 +46,7 @@ import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.Schema.AppMetadataTable; import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols; +import org.fdroid.fdroid.views.main.MainActivity; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -83,6 +84,7 @@ public class AppListActivity extends AppCompatActivity implements LoaderManager. private TextView emptyState; private EditText searchInput; private ImageView sortImage; + private View hiddenAppNotice; private Utils.KeyboardStateMonitor keyboardStateMonitor; private interface SortClause { @@ -152,6 +154,15 @@ public class AppListActivity extends AppCompatActivity implements LoaderManager. } }); + hiddenAppNotice = findViewById(R.id.hiddenAppNotice); + hiddenAppNotice.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(getApplicationContext(), MainActivity.class); + intent.putExtra(MainActivity.EXTRA_VIEW_SETTINGS, true); + getApplicationContext().startActivity(intent); + } + }); emptyState = (TextView) findViewById(R.id.empty_state); View backButton = findViewById(R.id.back); @@ -225,6 +236,10 @@ public class AppListActivity extends AppCompatActivity implements LoaderManager. return string.toString(); } + private void setShowHiddenAppsNotice(boolean show) { + hiddenAppNotice.setVisibility(show ? View.VISIBLE : View.GONE); + } + @NonNull @Override public Loader onCreateLoader(int id, Bundle args) { @@ -240,6 +255,8 @@ public class AppListActivity extends AppCompatActivity implements LoaderManager. @Override public void onLoadFinished(@NonNull Loader loader, Cursor cursor) { + setShowHiddenAppsNotice(false); + appAdapter.setHasHiddenAppsCallback(() -> setShowHiddenAppsNotice(true)); appAdapter.setAppCursor(cursor); if (cursor.getCount() > 0) { emptyState.setVisibility(View.GONE); diff --git a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListAdapter.java index e8581c0e0..742e8c66a 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListAdapter.java @@ -1,6 +1,7 @@ package org.fdroid.fdroid.views.apps; import android.database.Cursor; +import android.view.View; import android.view.ViewGroup; import org.fdroid.fdroid.R; @@ -14,12 +15,11 @@ import androidx.recyclerview.widget.RecyclerView; class AppListAdapter extends RecyclerView.Adapter { private Cursor cursor; + private Runnable hasHiddenAppsCallback; private final AppCompatActivity activity; - private final AppListItemDivider divider; AppListAdapter(AppCompatActivity activity) { this.activity = activity; - divider = new AppListItemDivider(activity); setHasStableIds(true); } @@ -28,6 +28,10 @@ class AppListAdapter extends RecyclerView.Adapter notifyDataSetChanged(); } + public void setHasHiddenAppsCallback(Runnable callback) { + hasHiddenAppsCallback = callback; + } + @NonNull @Override public StandardAppListItemController onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -40,6 +44,28 @@ class AppListAdapter extends RecyclerView.Adapter cursor.moveToPosition(position); final App app = new App(cursor); holder.bindModel(app); + + if (app.isDisabledByAntiFeatures()) { + holder.itemView.setVisibility(View.GONE); + holder.itemView.setLayoutParams( + new RecyclerView.LayoutParams( + 0, + 0 + ) + ); + + if (this.hasHiddenAppsCallback != null) { + this.hasHiddenAppsCallback.run(); + } + } else { + holder.itemView.setVisibility(View.VISIBLE); + holder.itemView.setLayoutParams( + new RecyclerView.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + ); + } } @Override @@ -52,16 +78,4 @@ class AppListAdapter extends RecyclerView.Adapter public int getItemCount() { return cursor == null ? 0 : cursor.getCount(); } - - @Override - public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { - super.onAttachedToRecyclerView(recyclerView); - recyclerView.addItemDecoration(divider); - } - - @Override - public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { - recyclerView.removeItemDecoration(divider); - super.onDetachedFromRecyclerView(recyclerView); - } } diff --git a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemDivider.java b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemDivider.java deleted file mode 100644 index b927e88d3..000000000 --- a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemDivider.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.fdroid.fdroid.views.apps; - -import android.content.Context; -import android.graphics.Rect; -import android.view.View; - -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.Utils; - -import androidx.core.content.ContextCompat; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.RecyclerView; - -/** - * Draws a faint line between items, to be used with the {@link AppListItemDivider}. - */ -public class AppListItemDivider extends DividerItemDecoration { - private final int itemSpacing; - - public AppListItemDivider(Context context) { - super(context, DividerItemDecoration.VERTICAL); - setDrawable(ContextCompat.getDrawable(context, R.drawable.app_list_item_divider)); - itemSpacing = Utils.dpToPx(8, context); - } - - @Override - public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { - super.getItemOffsets(outRect, view, parent, state); - int position = parent.getChildAdapterPosition(view); - if (position > 0) { - outRect.bottom = itemSpacing; - } - } -} diff --git a/app/src/main/java/org/fdroid/fdroid/views/apps/StandardAppListItemController.java b/app/src/main/java/org/fdroid/fdroid/views/apps/StandardAppListItemController.java index 86dc0f15b..29f3463b4 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/apps/StandardAppListItemController.java +++ b/app/src/main/java/org/fdroid/fdroid/views/apps/StandardAppListItemController.java @@ -38,7 +38,7 @@ public class StandardAppListItemController extends AppListItemController { private CharSequence getStatusText(@NonNull App app) { if (!app.compatible) { return activity.getString(R.string.app_incompatible); - } else if (app.isDisabledByAntiFeatures()) { + } else if (app.antiFeatures != null && app.antiFeatures.length > 0) { return activity.getString(R.string.antifeatures); } else if (app.isInstalled(activity.getApplicationContext())) { if (app.canAndWantToUpdate(activity)) { @@ -53,7 +53,7 @@ public class StandardAppListItemController extends AppListItemController { private boolean shouldShowInstall(@NonNull App app) { boolean installable = app.canAndWantToUpdate(activity) || !app.isInstalled(activity.getApplicationContext()); - boolean shouldAllow = app.compatible && !app.isDisabledByAntiFeatures(); + boolean shouldAllow = app.compatible && (app.antiFeatures == null || app.antiFeatures.length == 0); return installable && shouldAllow; } diff --git a/app/src/main/res/drawable/app_list_item_divider.xml b/app/src/main/res/drawable/app_list_item_divider.xml deleted file mode 100644 index 4b2c94fe0..000000000 --- a/app/src/main/res/drawable/app_list_item_divider.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_app_list.xml b/app/src/main/res/layout/activity_app_list.xml index af1048590..fe14d918b 100644 --- a/app/src/main/res/layout/activity_app_list.xml +++ b/app/src/main/res/layout/activity_app_list.xml @@ -82,6 +82,23 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/search_card"/> + + @@ -103,7 +120,7 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@+id/search_card" + app:layout_constraintTop_toBottomOf="@+id/hiddenAppNotice" android:visibility="visible" android:scrollbars="vertical" /> diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml index 9df050516..2cee282bc 100644 --- a/app/src/main/res/values/array.xml +++ b/app/src/main/res/values/array.xml @@ -15,4 +15,29 @@ @string/theme_follow_system + + @string/antiads_key + @string/antitrack_key + @string/antinonfreead_key + @string/antinonfreenet_key + @string/antinonfreedep_key + @string/antiupstreamnonfree_key + @string/antinonfreeassets_key + @string/antidisabledalgorithm_key + @string/antiknownvuln_key + @string/antinosource_key + + + + @string/antiads + @string/antitrack + @string/antinonfreead + @string/antinonfreenet + @string/antinonfreedep + @string/antiupstreamnonfree + @string/antinonfreeassets + @string/antidisabledalgorithm + @string/antiknownvuln + @string/antinosource + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c5632eb4f..619e9a5cd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -128,6 +128,7 @@ This often occurs with apps installed via Google Play or other sources, if they Repository: %1$s Size: %1$s + Some results were hidden based on your antifeature settings. Downloading %1$s %1$s installed Downloaded, ready to install @@ -280,7 +281,27 @@ This often occurs with apps installed via Google Play or other sources, if they New in version %s This app has features you may not like. + Ads + Tracking + NonFreeNet + NonFreeAdd + NonFreeDep + UpstreamNonFree + NonFreeAssets + DisabledAlgorithm + KnownVuln + NoSourceSince Anti-features + Ads + Tracking + Non-Free Addons + Non-Free Network Services + Non-Free Dependencies + Upstream Non-Free + Non-Free Assets + Signed Using An Unsafe Algorithm + Known Vulnerability + Source Code No Longer Available This app contains advertising This app tracks and reports your activity This app promotes non-free add-ons diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 6f5e409f4..71251c3e3 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -84,10 +84,12 @@ android:defaultValue="false" android:key="incompatibleVersions" android:title="@string/show_incompat_versions" /> - +