From 7424220c0212da7c773e88568dec8a461fca92a5 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Wed, 5 Jul 2017 17:21:09 +1000 Subject: [PATCH] Add apps with known vulnerabilities to updates tab. Currently only supports "Uninstall", but will soon also support "Upgrade to a newer version". --- .../views/apps/AppListItemController.java | 96 ++++++++++--------- .../fdroid/views/updates/UpdatesAdapter.java | 79 ++++++++++----- .../views/updates/items/KnownVulnApp.java | 60 ++++++++++++ .../items/KnownVulnAppListItemController.java | 78 +++++++++++++++ .../res/drawable/ic_known_vuln_overlay.xml | 19 ++++ .../res/layout/known_vuln_app_list_item.xml | 66 +++++++++++++ app/src/main/res/values/strings.xml | 3 +- .../fdroid/updater/IndexV1UpdaterTest.java | 7 ++ 8 files changed, 335 insertions(+), 73 deletions(-) create mode 100644 app/src/main/java/org/fdroid/fdroid/views/updates/items/KnownVulnApp.java create mode 100644 app/src/main/java/org/fdroid/fdroid/views/updates/items/KnownVulnAppListItemController.java create mode 100644 app/src/main/res/drawable/ic_known_vuln_overlay.xml create mode 100644 app/src/main/res/layout/known_vuln_app_list_item.xml diff --git a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java index 93c2f7489..8476e04ad 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java +++ b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java @@ -388,55 +388,59 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder { return; } - // When the button says "Run", then launch the app. - if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.Installed) { - Intent intent = activity.getPackageManager().getLaunchIntentForPackage(currentApp.packageName); - if (intent != null) { - activity.startActivity(intent); - - // Once it is explicitly launched by the user, then we can pretty much forget about - // any sort of notification that the app was successfully installed. It should be - // apparent to the user because they just launched it. - AppUpdateStatusManager.getInstance(activity).removeApk(currentStatus.getUniqueKey()); - } - return; - } - - if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.ReadyToInstall) { - Uri apkDownloadUri = Uri.parse(currentStatus.apk.getUrl()); - File apkFilePath = ApkCache.getApkDownloadPath(activity, apkDownloadUri); - Utils.debugLog(TAG, "skip download, we have already downloaded " + currentStatus.apk.getUrl() + - " to " + apkFilePath); - - // TODO: This seems like a bit of a hack. Is there a better way to do this by changing - // the Installer API so that we can ask it to install without having to get it to fire - // off an intent which we then listen for and action? - final LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(activity); - final BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - broadcastManager.unregisterReceiver(this); - - if (Installer.ACTION_INSTALL_USER_INTERACTION.equals(intent.getAction())) { - PendingIntent pendingIntent = - intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI); - try { - pendingIntent.send(); - } catch (PendingIntent.CanceledException ignored) { } - } - } - }; - - broadcastManager.registerReceiver(receiver, Installer.getInstallIntentFilter(apkDownloadUri)); - Installer installer = InstallerFactory.create(activity, currentStatus.apk); - installer.installPackage(Uri.parse(apkFilePath.toURI().toString()), apkDownloadUri); - } else { - final Apk suggestedApk = ApkProvider.Helper.findSuggestedApk(activity, currentApp); - InstallManagerService.queue(activity, currentApp, suggestedApk); - } + onActionButtonPressed(currentApp); } }; + protected void onActionButtonPressed(@NonNull App app) { + // When the button says "Run", then launch the app. + if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.Installed) { + Intent intent = activity.getPackageManager().getLaunchIntentForPackage(app.packageName); + if (intent != null) { + activity.startActivity(intent); + + // Once it is explicitly launched by the user, then we can pretty much forget about + // any sort of notification that the app was successfully installed. It should be + // apparent to the user because they just launched it. + AppUpdateStatusManager.getInstance(activity).removeApk(currentStatus.getUniqueKey()); + } + return; + } + + if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.ReadyToInstall) { + Uri apkDownloadUri = Uri.parse(currentStatus.apk.getUrl()); + File apkFilePath = ApkCache.getApkDownloadPath(activity, apkDownloadUri); + Utils.debugLog(TAG, "skip download, we have already downloaded " + currentStatus.apk.getUrl() + + " to " + apkFilePath); + + // TODO: This seems like a bit of a hack. Is there a better way to do this by changing + // the Installer API so that we can ask it to install without having to get it to fire + // off an intent which we then listen for and action? + final LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(activity); + final BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + broadcastManager.unregisterReceiver(this); + + if (Installer.ACTION_INSTALL_USER_INTERACTION.equals(intent.getAction())) { + PendingIntent pendingIntent = + intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI); + try { + pendingIntent.send(); + } catch (PendingIntent.CanceledException ignored) { } + } + } + }; + + broadcastManager.registerReceiver(receiver, Installer.getInstallIntentFilter(apkDownloadUri)); + Installer installer = InstallerFactory.create(activity, currentStatus.apk); + installer.installPackage(Uri.parse(apkFilePath.toURI().toString()), apkDownloadUri); + } else { + final Apk suggestedApk = ApkProvider.Helper.findSuggestedApk(activity, app); + InstallManagerService.queue(activity, app, suggestedApk); + } + } + @SuppressWarnings("FieldCanBeLocal") private final View.OnClickListener onCancelDownload = new View.OnClickListener() { @Override diff --git a/app/src/main/java/org/fdroid/fdroid/views/updates/UpdatesAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/updates/UpdatesAdapter.java index dfa046171..a993910a6 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/updates/UpdatesAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/updates/UpdatesAdapter.java @@ -5,6 +5,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.Cursor; +import android.net.Uri; import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; @@ -20,6 +21,7 @@ import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.Schema; import org.fdroid.fdroid.views.updates.items.AppStatus; import org.fdroid.fdroid.views.updates.items.AppUpdateData; +import org.fdroid.fdroid.views.updates.items.KnownVulnApp; import org.fdroid.fdroid.views.updates.items.UpdateableApp; import org.fdroid.fdroid.views.updates.items.UpdateableAppsHeader; @@ -65,6 +67,9 @@ import java.util.Set; public class UpdatesAdapter extends RecyclerView.Adapter implements LoaderManager.LoaderCallbacks { + private static final int LOADER_CAN_UPDATE = 289753982; + private static final int LOADER_KNOWN_VULN = 520389740; + private final AdapterDelegatesManager> delegatesManager = new AdapterDelegatesManager<>(); private final List items = new ArrayList<>(); @@ -72,6 +77,7 @@ public class UpdatesAdapter extends RecyclerView.Adapter appsToShowStatus = new ArrayList<>(); private final List updateableApps = new ArrayList<>(); + private final List knownVulnApps = new ArrayList<>(); private boolean showAllUpdateableApps = false; @@ -80,9 +86,11 @@ public class UpdatesAdapter extends RecyclerView.Adapter onCreateLoader(int id, Bundle args) { + Uri uri; + switch (id) { + case LOADER_CAN_UPDATE: + uri = AppProvider.getCanUpdateUri(); + break; + + case LOADER_KNOWN_VULN: + uri = AppProvider.getInstalledWithKnownVulnsUri(); + break; + + default: + throw new IllegalStateException("Unknown loader requested: " + id); + } + return new CursorLoader( - activity, - AppProvider.getCanUpdateUri(), - new String[]{ - Schema.AppMetadataTable.Cols._ID, // Required for cursor loader to work. - Schema.AppMetadataTable.Cols.Package.PACKAGE_NAME, - Schema.AppMetadataTable.Cols.NAME, - Schema.AppMetadataTable.Cols.SUMMARY, - Schema.AppMetadataTable.Cols.IS_COMPATIBLE, - Schema.AppMetadataTable.Cols.LICENSE, - Schema.AppMetadataTable.Cols.ICON, - Schema.AppMetadataTable.Cols.ICON_URL, - Schema.AppMetadataTable.Cols.InstalledApp.VERSION_CODE, - Schema.AppMetadataTable.Cols.InstalledApp.VERSION_NAME, - Schema.AppMetadataTable.Cols.SuggestedApk.VERSION_NAME, - Schema.AppMetadataTable.Cols.SUGGESTED_VERSION_CODE, - Schema.AppMetadataTable.Cols.REQUIREMENTS, // Needed for filtering apps that require root. - Schema.AppMetadataTable.Cols.ANTI_FEATURES, // Needed for filtering apps that require anti-features. - }, - null, - null, - Schema.AppMetadataTable.Cols.NAME - ); + activity, uri, Schema.AppMetadataTable.Cols.ALL, null, null, Schema.AppMetadataTable.Cols.NAME); } @Override public void onLoadFinished(Loader loader, Cursor cursor) { + switch (loader.getId()) { + case LOADER_CAN_UPDATE: + onCanUpdateLoadFinished(cursor); + break; + + case LOADER_KNOWN_VULN: + onKnownVulnLoadFinished(cursor); + break; + } + + populateItems(); + notifyDataSetChanged(); + } + + private void onCanUpdateLoadFinished(Cursor cursor) { updateableApps.clear(); cursor.moveToFirst(); @@ -220,9 +240,16 @@ public class UpdatesAdapter extends RecyclerView.Adapter> { + + private final Activity activity; + + public Delegate(Activity activity) { + this.activity = activity; + } + + @Override + protected boolean isForViewType(@NonNull List items, int position) { + return items.get(position) instanceof KnownVulnApp; + } + + @NonNull + @Override + protected RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) { + return new KnownVulnAppListItemController(activity, activity.getLayoutInflater() + .inflate(R.layout.known_vuln_app_list_item, parent, false)); + } + + @Override + protected void onBindViewHolder(@NonNull List items, int position, + @NonNull RecyclerView.ViewHolder holder, @NonNull List payloads) { + KnownVulnApp app = (KnownVulnApp) items.get(position); + ((KnownVulnAppListItemController) holder).bindModel(app.app); + } + } + +} diff --git a/app/src/main/java/org/fdroid/fdroid/views/updates/items/KnownVulnAppListItemController.java b/app/src/main/java/org/fdroid/fdroid/views/updates/items/KnownVulnAppListItemController.java new file mode 100644 index 000000000..1641001d9 --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/views/updates/items/KnownVulnAppListItemController.java @@ -0,0 +1,78 @@ +package org.fdroid.fdroid.views.updates.items; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.content.LocalBroadcastManager; +import android.view.View; + +import org.fdroid.fdroid.AppUpdateStatusManager; +import org.fdroid.fdroid.R; +import org.fdroid.fdroid.data.App; +import org.fdroid.fdroid.data.AppProvider; +import org.fdroid.fdroid.installer.Installer; +import org.fdroid.fdroid.installer.InstallerService; +import org.fdroid.fdroid.views.apps.AppListItemController; +import org.fdroid.fdroid.views.apps.AppListItemState; + +/** + * Tell the user that an app they have installed has a known vulnerability. + * The role of this controller is to prompt the user what it is that should be done in response to this + * (e.g. uninstall, update, disable). + */ +public class KnownVulnAppListItemController extends AppListItemController { + public KnownVulnAppListItemController(Activity activity, View itemView) { + super(activity, itemView); + } + + @NonNull + @Override + protected AppListItemState getCurrentViewState( + @NonNull App app, @Nullable AppUpdateStatusManager.AppUpdateStatus appStatus) { + return new AppListItemState(app) + .setMainText(activity.getString(R.string.updates__app_with_known_vulnerability__uninstall, app.name)) + .showActionButton(activity.getString(R.string.menu_uninstall)); + } + + @Override + protected void onActionButtonPressed(@NonNull App app) { + LocalBroadcastManager.getInstance(activity).registerReceiver(uninstallReceiver, + Installer.getUninstallIntentFilter(app.packageName)); + InstallerService.uninstall(activity, app.getInstalledApk(activity)); + } + + private void unregisterUninstallReceiver() { + LocalBroadcastManager.getInstance(activity).unregisterReceiver(uninstallReceiver); + } + + private final BroadcastReceiver uninstallReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case Installer.ACTION_UNINSTALL_COMPLETE: + // This will cause the LoaderManager in UpdatesAdapter to automatically requery for the list of + // apps with known vulnerabilities (i.e. this app should no longer be in that list). + activity.getContentResolver().notifyChange(AppProvider.getInstalledWithKnownVulnsUri(), null); + unregisterUninstallReceiver(); + break; + + case Installer.ACTION_UNINSTALL_INTERRUPTED: + unregisterUninstallReceiver(); + break; + + case Installer.ACTION_UNINSTALL_USER_INTERACTION: + PendingIntent uninstallPendingIntent = + intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI); + + try { + uninstallPendingIntent.send(); + } catch (PendingIntent.CanceledException ignored) { } + break; + } + } + }; +} diff --git a/app/src/main/res/drawable/ic_known_vuln_overlay.xml b/app/src/main/res/drawable/ic_known_vuln_overlay.xml new file mode 100644 index 000000000..382d6dfe3 --- /dev/null +++ b/app/src/main/res/drawable/ic_known_vuln_overlay.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/app/src/main/res/layout/known_vuln_app_list_item.xml b/app/src/main/res/layout/known_vuln_app_list_item.xml new file mode 100644 index 000000000..6420162fd --- /dev/null +++ b/app/src/main/res/layout/known_vuln_app_list_item.xml @@ -0,0 +1,66 @@ + + + + + + + + + + +