Merge branch 'issue-1115--no-versions-multisig' into 'master'

Be more helpful when no versions available due to mismatching signatures.

Closes #1115

See merge request !577
This commit is contained in:
Hans-Christoph Steiner 2017-09-05 10:10:14 +00:00
commit 5144aedec9
3 changed files with 129 additions and 21 deletions

View File

@ -3,6 +3,7 @@ package org.fdroid.fdroid.views;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@ -12,6 +13,7 @@ import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewCompat;
import android.support.v4.widget.TextViewCompat; import android.support.v4.widget.TextViewCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.GridLayout; import android.support.v7.widget.GridLayout;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
@ -47,6 +49,7 @@ import org.fdroid.fdroid.data.InstalledAppProvider;
import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.privileged.views.AppDiff; import org.fdroid.fdroid.privileged.views.AppDiff;
import org.fdroid.fdroid.privileged.views.AppSecurityPermissions; import org.fdroid.fdroid.privileged.views.AppSecurityPermissions;
import org.fdroid.fdroid.views.main.MainActivity;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.ArrayList; import java.util.ArrayList;
@ -94,8 +97,9 @@ public class AppDetailsRecyclerViewAdapter
private App app; private App app;
private final AppDetailsRecyclerViewAdapterCallbacks callbacks; private final AppDetailsRecyclerViewAdapterCallbacks callbacks;
private RecyclerView recyclerView; private RecyclerView recyclerView;
private ArrayList<Object> items; private List<Object> items;
private ArrayList<Apk> versions; private List<Apk> versions;
private List<Apk> compatibleVersionsDifferentSig;
private boolean showVersions; private boolean showVersions;
private HeaderViewHolder headerView; private HeaderViewHolder headerView;
@ -112,12 +116,17 @@ public class AppDetailsRecyclerViewAdapter
// Get versions // Get versions
versions = new ArrayList<>(); versions = new ArrayList<>();
compatibleVersionsDifferentSig = new ArrayList<>();
final List<Apk> apks = ApkProvider.Helper.findByPackageName(context, this.app.packageName); final List<Apk> apks = ApkProvider.Helper.findByPackageName(context, this.app.packageName);
boolean showIncompatibleVersions = Preferences.get().showIncompatibleVersions();
for (final Apk apk : apks) { for (final Apk apk : apks) {
boolean allowByCompatability = apk.compatible || Preferences.get().showIncompatibleVersions(); boolean allowByCompatability = apk.compatible || showIncompatibleVersions;
boolean allowBySig = this.app.installedSig == null || TextUtils.equals(this.app.installedSig, apk.sig); boolean allowBySig = this.app.installedSig == null || showIncompatibleVersions || TextUtils.equals(this.app.installedSig, apk.sig);
if (allowByCompatability && allowBySig) { if (allowByCompatability) {
versions.add(apk); compatibleVersionsDifferentSig.add(apk);
if (allowBySig) {
versions.add(apk);
}
} }
} }
@ -226,8 +235,12 @@ public class AppDetailsRecyclerViewAdapter
View permissions = inflater.inflate(R.layout.app_details2_links, parent, false); View permissions = inflater.inflate(R.layout.app_details2_links, parent, false);
return new PermissionsViewHolder(permissions); return new PermissionsViewHolder(permissions);
case VIEWTYPE_VERSIONS: case VIEWTYPE_VERSIONS:
View versions = inflater.inflate(R.layout.app_details2_links, parent, false); View versionsView = inflater.inflate(R.layout.app_details2_links, parent, false);
return new VersionsViewHolder(versions); if (versions.size() == 0) {
return new NoVersionsViewHolder(versionsView);
} else {
return new VersionsViewHolder(versionsView);
}
case VIEWTYPE_VERSION: case VIEWTYPE_VERSION:
View version = inflater.inflate(R.layout.apklistitem, parent, false); View version = inflater.inflate(R.layout.apklistitem, parent, false);
return new VersionViewHolder(version); return new VersionViewHolder(version);
@ -244,21 +257,16 @@ public class AppDetailsRecyclerViewAdapter
headerView = header; headerView = header;
header.bindModel(); header.bindModel();
break; break;
// These don't have any specific requirements, they all get their state from the outer class.
case VIEWTYPE_SCREENSHOTS: case VIEWTYPE_SCREENSHOTS:
((ScreenShotsViewHolder) holder).bindModel();
break;
case VIEWTYPE_DONATE: case VIEWTYPE_DONATE:
((DonateViewHolder) holder).bindModel();
break;
case VIEWTYPE_LINKS: case VIEWTYPE_LINKS:
((LinksViewHolder) holder).bindModel();
break;
case VIEWTYPE_PERMISSIONS: case VIEWTYPE_PERMISSIONS:
((PermissionsViewHolder) holder).bindModel();
break;
case VIEWTYPE_VERSIONS: case VIEWTYPE_VERSIONS:
((VersionsViewHolder) holder).bindModel(); ((AppDetailsViewHolder) holder).bindModel();
break; break;
case VIEWTYPE_VERSION: case VIEWTYPE_VERSION:
final Apk apk = (Apk) items.get(position); final Apk apk = (Apk) items.get(position);
((VersionViewHolder) holder).bindModel(apk); ((VersionViewHolder) holder).bindModel(apk);
@ -562,7 +570,15 @@ public class AppDetailsRecyclerViewAdapter
super.onDetachedFromRecyclerView(recyclerView); 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; final RecyclerView recyclerView;
LinearLayoutManagerSnapHelper snapHelper; LinearLayoutManagerSnapHelper snapHelper;
@ -571,6 +587,7 @@ public class AppDetailsRecyclerViewAdapter
recyclerView = (RecyclerView) view.findViewById(R.id.screenshots); recyclerView = (RecyclerView) view.findViewById(R.id.screenshots);
} }
@Override
public void bindModel() { public void bindModel() {
LinearLayoutManager lm = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false); LinearLayoutManager lm = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(lm); recyclerView.setLayoutManager(lm);
@ -586,7 +603,7 @@ public class AppDetailsRecyclerViewAdapter
} }
} }
private class DonateViewHolder extends RecyclerView.ViewHolder { private class DonateViewHolder extends AppDetailsViewHolder {
final TextView donateHeading; final TextView donateHeading;
final GridLayout donationOptionsLayout; final GridLayout donationOptionsLayout;
@ -596,6 +613,7 @@ public class AppDetailsRecyclerViewAdapter
donationOptionsLayout = (GridLayout) view.findViewById(R.id.donation_options); donationOptionsLayout = (GridLayout) view.findViewById(R.id.donation_options);
} }
@Override
public void bindModel() { public void bindModel() {
if (TextUtils.isEmpty(app.authorName)) { if (TextUtils.isEmpty(app.authorName)) {
donateHeading.setText(context.getString(R.string.app_details_donate_prompt_unknown_author, app.name)); donateHeading.setText(context.getString(R.string.app_details_donate_prompt_unknown_author, app.name));
@ -640,7 +658,7 @@ public class AppDetailsRecyclerViewAdapter
} }
} }
private abstract class ExpandableLinearLayoutViewHolder extends RecyclerView.ViewHolder { private abstract class ExpandableLinearLayoutViewHolder extends AppDetailsViewHolder {
final TextView headerView; final TextView headerView;
final LinearLayout contentView; final LinearLayout contentView;
@ -667,6 +685,7 @@ public class AppDetailsRecyclerViewAdapter
super(view); super(view);
} }
@Override
public void bindModel() { public void bindModel() {
itemView.setOnClickListener(new View.OnClickListener() { itemView.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -684,12 +703,80 @@ 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)
.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();
}
private boolean hasCompatibleApksDifferentSigs() {
return compatibleVersionsDifferentSig != null && compatibleVersionsDifferentSig.size() > 0;
}
}
private class PermissionsViewHolder extends ExpandableLinearLayoutViewHolder { private class PermissionsViewHolder extends ExpandableLinearLayoutViewHolder {
PermissionsViewHolder(View view) { PermissionsViewHolder(View view) {
super(view); super(view);
} }
@Override
public void bindModel() { public void bindModel() {
itemView.setOnClickListener(new View.OnClickListener() { itemView.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -721,6 +808,7 @@ public class AppDetailsRecyclerViewAdapter
super(view); super(view);
} }
@Override
public void bindModel() { public void bindModel() {
itemView.setOnClickListener(new View.OnClickListener() { itemView.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -875,12 +963,18 @@ public class AppDetailsRecyclerViewAdapter
nativecode.setVisibility(View.GONE); nativecode.setVisibility(View.GONE);
} }
boolean mismatchedSig = app.installedSig != null && !TextUtils.equals(app.installedSig, apk.sig);
if (apk.incompatibleReasons != null) { if (apk.incompatibleReasons != null) {
incompatibleReasons.setText( incompatibleReasons.setText(
context.getResources().getString( context.getResources().getString(
R.string.requires_features, R.string.requires_features,
TextUtils.join(", ", apk.incompatibleReasons))); TextUtils.join(", ", apk.incompatibleReasons)));
incompatibleReasons.setVisibility(View.VISIBLE); incompatibleReasons.setVisibility(View.VISIBLE);
} else if (mismatchedSig) {
incompatibleReasons.setText(
context.getString(R.string.app_details__incompatible_mismatched_signature));
incompatibleReasons.setVisibility(View.VISIBLE);
} else { } else {
incompatibleReasons.setVisibility(View.GONE); incompatibleReasons.setVisibility(View.GONE);
} }
@ -898,7 +992,7 @@ public class AppDetailsRecyclerViewAdapter
nativecode, nativecode,
}; };
for (final View v : views) { for (final View v : views) {
v.setEnabled(apk.compatible); v.setEnabled(apk.compatible && !mismatchedSig);
} }
itemView.setOnClickListener(new View.OnClickListener() { itemView.setOnClickListener(new View.OnClickListener() {
@Override @Override

View File

@ -54,6 +54,7 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB
private static final String TAG = "MainActivity"; 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_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"; 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)); pager.scrollToPosition(adapter.adapterPositionFromItemId(R.id.updates));
selectedMenuId = R.id.updates; selectedMenuId = R.id.updates;
setSelectedMenuInNav(); 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 // AppDetails2 and RepoDetailsActivity set different NFC actions, so reset here

View File

@ -54,6 +54,14 @@
<string name="app_details_donate_prompt_unknown_author">Buy the developers of %1$s a coffee!</string> <string name="app_details_donate_prompt_unknown_author">Buy the developers of %1$s a coffee!</string>
<string name="app_details_donate_prompt">%1$s is created by %2$s. Buy them a coffee!</string> <string name="app_details_donate_prompt">%1$s is created by %2$s. Buy them a coffee!</string>
<string name="app_details__incompatible_mismatched_signature">Different signature to installed version</string>
<string name="app_details__no_versions__show_incompat_versions">To show incompatible versions here anyway, enable the \"%1$s\" setting.</string>
<string name="app_details__no_versions__no_compatible_signatures">No versions with compatible signature</string>
<string name="app_details__no_versions__none_compatible_with_device">No versions compatible with device</string>
<string name="app_details__no_versions__explain_incompatible_signatures">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.</string>
<string name="about_title">About F-Droid</string> <string name="about_title">About F-Droid</string>
<string name="about_version">Version</string> <string name="about_version">Version</string>
<string name="about_site">Website</string> <string name="about_site">Website</string>