From 68e0f16b7c615d4fac0d1a353a418c807e36cdb6 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 8 Aug 2017 09:15:14 +1000 Subject: [PATCH 1/4] Minor simplification to view binding for app details. Instead of casting to more specific view holders, all to call the similarly named/behaved "bindModel()" method, push this to a base class. --- .../views/AppDetailsRecyclerViewAdapter.java | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) 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 2d82805db..4af3004d4 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java @@ -94,8 +94,8 @@ public class AppDetailsRecyclerViewAdapter private App app; private final AppDetailsRecyclerViewAdapterCallbacks callbacks; private RecyclerView recyclerView; - private ArrayList items; - private ArrayList versions; + private List items; + private List versions; private boolean showVersions; private HeaderViewHolder headerView; @@ -244,21 +244,16 @@ public class AppDetailsRecyclerViewAdapter headerView = header; header.bindModel(); break; + + // These don't have any specific requirements, they all get their state from the outer class. case VIEWTYPE_SCREENSHOTS: - ((ScreenShotsViewHolder) holder).bindModel(); - break; case VIEWTYPE_DONATE: - ((DonateViewHolder) holder).bindModel(); - break; case VIEWTYPE_LINKS: - ((LinksViewHolder) holder).bindModel(); - break; case VIEWTYPE_PERMISSIONS: - ((PermissionsViewHolder) holder).bindModel(); - break; case VIEWTYPE_VERSIONS: - ((VersionsViewHolder) holder).bindModel(); + ((AppDetailsViewHolder) holder).bindModel(); break; + case VIEWTYPE_VERSION: final Apk apk = (Apk) items.get(position); ((VersionViewHolder) holder).bindModel(apk); @@ -562,7 +557,15 @@ public class AppDetailsRecyclerViewAdapter super.onDetachedFromRecyclerView(recyclerView); } - private class ScreenShotsViewHolder extends RecyclerView.ViewHolder { + private abstract class AppDetailsViewHolder extends RecyclerView.ViewHolder { + AppDetailsViewHolder(View itemView) { + super(itemView); + } + + public abstract void bindModel(); + } + + private class ScreenShotsViewHolder extends AppDetailsViewHolder { final RecyclerView recyclerView; LinearLayoutManagerSnapHelper snapHelper; @@ -571,6 +574,7 @@ public class AppDetailsRecyclerViewAdapter recyclerView = (RecyclerView) view.findViewById(R.id.screenshots); } + @Override public void bindModel() { LinearLayoutManager lm = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false); recyclerView.setLayoutManager(lm); @@ -586,7 +590,7 @@ public class AppDetailsRecyclerViewAdapter } } - private class DonateViewHolder extends RecyclerView.ViewHolder { + private class DonateViewHolder extends AppDetailsViewHolder { final TextView donateHeading; final GridLayout donationOptionsLayout; @@ -596,6 +600,7 @@ public class AppDetailsRecyclerViewAdapter donationOptionsLayout = (GridLayout) view.findViewById(R.id.donation_options); } + @Override public void bindModel() { if (TextUtils.isEmpty(app.authorName)) { donateHeading.setText(context.getString(R.string.app_details_donate_prompt_unknown_author, app.name)); @@ -640,7 +645,7 @@ public class AppDetailsRecyclerViewAdapter } } - private abstract class ExpandableLinearLayoutViewHolder extends RecyclerView.ViewHolder { + private abstract class ExpandableLinearLayoutViewHolder extends AppDetailsViewHolder { final TextView headerView; final LinearLayout contentView; @@ -667,6 +672,7 @@ public class AppDetailsRecyclerViewAdapter super(view); } + @Override public void bindModel() { itemView.setOnClickListener(new View.OnClickListener() { @Override @@ -690,6 +696,7 @@ public class AppDetailsRecyclerViewAdapter super(view); } + @Override public void bindModel() { itemView.setOnClickListener(new View.OnClickListener() { @Override @@ -721,6 +728,7 @@ public class AppDetailsRecyclerViewAdapter super(view); } + @Override public void bindModel() { itemView.setOnClickListener(new View.OnClickListener() { @Override From ef717437a9e2abf1e4abb7eb72941e69530a837a Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 8 Aug 2017 10:01:43 +1000 Subject: [PATCH 2/4] Explain multi-sig problems to user in app details. --- .../views/AppDetailsRecyclerViewAdapter.java | 76 ++++++++++++++++++- app/src/main/res/values/strings.xml | 7 ++ 2 files changed, 79 insertions(+), 4 deletions(-) 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 4af3004d4..61ab2c8d6 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java @@ -12,6 +12,7 @@ import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.v4.view.ViewCompat; import android.support.v4.widget.TextViewCompat; +import android.support.v7.app.AlertDialog; import android.support.v7.widget.GridLayout; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -96,6 +97,7 @@ public class AppDetailsRecyclerViewAdapter private RecyclerView recyclerView; private List items; private List versions; + private List compatibleVersionsDifferentSig; private boolean showVersions; private HeaderViewHolder headerView; @@ -112,12 +114,16 @@ public class AppDetailsRecyclerViewAdapter // Get versions versions = new ArrayList<>(); + compatibleVersionsDifferentSig = new ArrayList<>(); final List apks = ApkProvider.Helper.findByPackageName(context, this.app.packageName); for (final Apk apk : apks) { boolean allowByCompatability = apk.compatible || Preferences.get().showIncompatibleVersions(); boolean allowBySig = this.app.installedSig == null || TextUtils.equals(this.app.installedSig, apk.sig); - if (allowByCompatability && allowBySig) { - versions.add(apk); + if (allowByCompatability) { + compatibleVersionsDifferentSig.add(apk); + if (allowBySig) { + versions.add(apk); + } } } @@ -226,8 +232,12 @@ public class AppDetailsRecyclerViewAdapter View permissions = inflater.inflate(R.layout.app_details2_links, parent, false); return new PermissionsViewHolder(permissions); case VIEWTYPE_VERSIONS: - View versions = inflater.inflate(R.layout.app_details2_links, parent, false); - return new VersionsViewHolder(versions); + View versionsView = inflater.inflate(R.layout.app_details2_links, parent, false); + if (versions.size() == 0) { + return new NoVersionsViewHolder(versionsView); + } else { + return new VersionsViewHolder(versionsView); + } case VIEWTYPE_VERSION: View version = inflater.inflate(R.layout.apklistitem, parent, false); return new VersionViewHolder(version); @@ -690,6 +700,64 @@ public class AppDetailsRecyclerViewAdapter } } + private class NoVersionsViewHolder extends AppDetailsViewHolder { + final TextView headerView; + + NoVersionsViewHolder(View view) { + super(view); + headerView = (TextView) view.findViewById(R.id.information); + TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(headerView, R.drawable.ic_access_time_24dp_grey600, 0, 0, 0); + + itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + explainIncompatibleVersions(); + } + }); + } + + @Override + public void bindModel() { + if (hasCompatibleApksDifferentSigs()) { + headerView.setText("No versions with compatible signature"); + } else { + headerView.setText("No versions compatible with device"); + } + } + + /** + * Show a dialog to the user explaining the reaons there are no compatible versions. + * This will either be due to device features (e.g. NFC, API levels, etc) or being signed + * by a different certificate (as is often the case with apps from Google Play signed by + * upstream). + */ + private void explainIncompatibleVersions() { + String preferenceName = context.getString(R.string.show_incompat_versions); + String showIncompatible = context.getString( + R.string.app_details__no_versions__show_incompat_versions, preferenceName); + + String message; + String title; + if (hasCompatibleApksDifferentSigs()) { + title = context.getString(R.string.app_details__no_versions__no_compatible_signatures); + message = context.getString(R.string.app_details__no_versions__explain_incompatible_signatures) + + "\n\n" + showIncompatible; + } else { + title = context.getString(R.string.app_details__no_versions__none_compatible_with_device); + message = showIncompatible; + } + + new AlertDialog.Builder(context) + .setTitle(title) + .setMessage(message) + .show(); + } + + private boolean hasCompatibleApksDifferentSigs() { + return compatibleVersionsDifferentSig != null && compatibleVersionsDifferentSig.size() > 0; + } + } + private class PermissionsViewHolder extends ExpandableLinearLayoutViewHolder { PermissionsViewHolder(View view) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c3e01cc93..6136cb79d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -54,6 +54,13 @@ Buy the developers of %1$s a coffee! %1$s is created by %2$s. Buy them a coffee! + To show incompatible versions here anyway, enable the \"%1$s\" setting. + No versions with compatible signature + No versions compatible with device + The installed version is not compatible with any available versions. Uninstalling the app will enable you to view and install compatible versions. + +This often occurs with apps installed via Google Play or other sources, if they are signed by a different certificate. + About F-Droid Version Website From dd93df9ef224d545ea91777d151539f9f2b2eb9e Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 8 Aug 2017 10:25:55 +1000 Subject: [PATCH 3/4] Show mismatching sig apks when incompatible versions enabled. This is in the spirit of the setting, where users can see which apks are available even though they are not installable. Adds a message explaining why it is incompatible (i.e. because the signature doesn't match the installed version). --- .../fdroid/views/AppDetailsRecyclerViewAdapter.java | 13 ++++++++++--- app/src/main/res/values/strings.xml | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) 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 61ab2c8d6..5dea02a26 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java @@ -116,9 +116,10 @@ public class AppDetailsRecyclerViewAdapter versions = new ArrayList<>(); compatibleVersionsDifferentSig = new ArrayList<>(); final List apks = ApkProvider.Helper.findByPackageName(context, this.app.packageName); + boolean showIncompatibleVersions = Preferences.get().showIncompatibleVersions(); for (final Apk apk : apks) { - boolean allowByCompatability = apk.compatible || Preferences.get().showIncompatibleVersions(); - boolean allowBySig = this.app.installedSig == null || TextUtils.equals(this.app.installedSig, apk.sig); + boolean allowByCompatability = apk.compatible || showIncompatibleVersions; + boolean allowBySig = this.app.installedSig == null || showIncompatibleVersions || TextUtils.equals(this.app.installedSig, apk.sig); if (allowByCompatability) { compatibleVersionsDifferentSig.add(apk); if (allowBySig) { @@ -951,12 +952,18 @@ public class AppDetailsRecyclerViewAdapter nativecode.setVisibility(View.GONE); } + boolean mismatchedSig = app.installedSig != null && !TextUtils.equals(app.installedSig, apk.sig); + if (apk.incompatibleReasons != null) { incompatibleReasons.setText( context.getResources().getString( R.string.requires_features, TextUtils.join(", ", apk.incompatibleReasons))); incompatibleReasons.setVisibility(View.VISIBLE); + } else if (mismatchedSig) { + incompatibleReasons.setText( + context.getString(R.string.app_details__incompatible_mismatched_signature)); + incompatibleReasons.setVisibility(View.VISIBLE); } else { incompatibleReasons.setVisibility(View.GONE); } @@ -974,7 +981,7 @@ public class AppDetailsRecyclerViewAdapter nativecode, }; for (final View v : views) { - v.setEnabled(apk.compatible); + v.setEnabled(apk.compatible && !mismatchedSig); } itemView.setOnClickListener(new View.OnClickListener() { @Override diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6136cb79d..ab5e19c1c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -54,6 +54,7 @@ Buy the developers of %1$s a coffee! %1$s is created by %2$s. Buy them a coffee! + Different signature to installed version To show incompatible versions here anyway, enable the \"%1$s\" setting. No versions with compatible signature No versions compatible with device From ba96819e40906fa83b9f52e84e7275f055cb7bc8 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 22 Aug 2017 08:05:13 +1000 Subject: [PATCH 4/4] Prompt user to go to settings to help with incompatible versions --- .../fdroid/views/AppDetailsRecyclerViewAdapter.java | 11 +++++++++++ .../org/fdroid/fdroid/views/main/MainActivity.java | 6 ++++++ 2 files changed, 17 insertions(+) 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 5dea02a26..e2ee5eae7 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java @@ -3,6 +3,7 @@ package org.fdroid.fdroid.views; import android.annotation.SuppressLint; import android.content.ActivityNotFoundException; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; @@ -48,6 +49,7 @@ import org.fdroid.fdroid.data.InstalledAppProvider; import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.privileged.views.AppDiff; import org.fdroid.fdroid.privileged.views.AppSecurityPermissions; +import org.fdroid.fdroid.views.main.MainActivity; import java.text.NumberFormat; import java.util.ArrayList; @@ -751,6 +753,15 @@ public class AppDetailsRecyclerViewAdapter new AlertDialog.Builder(context) .setTitle(title) .setMessage(message) + .setPositiveButton(R.string.menu_settings, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(context, MainActivity.class); + intent.putExtra(MainActivity.EXTRA_VIEW_SETTINGS, true); + context.startActivity(intent); + } + }) + .setNegativeButton(R.string.cancel, null) .show(); } diff --git a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java index 7edc455ee..643e5fefa 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java @@ -54,6 +54,7 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB private static final String TAG = "MainActivity"; public static final String EXTRA_VIEW_UPDATES = "org.fdroid.fdroid.views.main.MainActivity.VIEW_UPDATES"; + public static final String EXTRA_VIEW_SETTINGS = "org.fdroid.fdroid.views.main.MainActivity.VIEW_SETTINGS"; private static final String ADD_REPO_INTENT_HANDLED = "addRepoIntentHandled"; @@ -156,6 +157,11 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB pager.scrollToPosition(adapter.adapterPositionFromItemId(R.id.updates)); selectedMenuId = R.id.updates; setSelectedMenuInNav(); + } else if (getIntent().hasExtra(EXTRA_VIEW_SETTINGS)) { + getIntent().removeExtra(EXTRA_VIEW_SETTINGS); + pager.scrollToPosition(adapter.adapterPositionFromItemId(R.id.settings)); + selectedMenuId = R.id.settings; + setSelectedMenuInNav(); } // AppDetails2 and RepoDetailsActivity set different NFC actions, so reset here