Add apps with known vulnerabilities to updates tab.
Currently only supports "Uninstall", but will soon also support "Upgrade to a newer version".
This commit is contained in:
parent
9b20142fd9
commit
7424220c02
@ -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
|
||||
|
@ -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<RecyclerView.ViewHolder>
|
||||
implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
private static final int LOADER_CAN_UPDATE = 289753982;
|
||||
private static final int LOADER_KNOWN_VULN = 520389740;
|
||||
|
||||
private final AdapterDelegatesManager<List<AppUpdateData>> delegatesManager = new AdapterDelegatesManager<>();
|
||||
private final List<AppUpdateData> items = new ArrayList<>();
|
||||
|
||||
@ -72,6 +77,7 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
|
||||
private final List<AppStatus> appsToShowStatus = new ArrayList<>();
|
||||
private final List<UpdateableApp> updateableApps = new ArrayList<>();
|
||||
private final List<KnownVulnApp> knownVulnApps = new ArrayList<>();
|
||||
|
||||
private boolean showAllUpdateableApps = false;
|
||||
|
||||
@ -80,9 +86,11 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
|
||||
delegatesManager.addDelegate(new AppStatus.Delegate(activity))
|
||||
.addDelegate(new UpdateableApp.Delegate(activity))
|
||||
.addDelegate(new UpdateableAppsHeader.Delegate(activity));
|
||||
.addDelegate(new UpdateableAppsHeader.Delegate(activity))
|
||||
.addDelegate(new KnownVulnApp.Delegate(activity));
|
||||
|
||||
activity.getSupportLoaderManager().initLoader(0, null, this);
|
||||
activity.getSupportLoaderManager().initLoader(LOADER_CAN_UPDATE, null, this);
|
||||
activity.getSupportLoaderManager().initLoader(LOADER_KNOWN_VULN, null, this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -162,6 +170,10 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (KnownVulnApp app : knownVulnApps) {
|
||||
items.add(app);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -186,33 +198,41 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> 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<Cursor> 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<RecyclerView.ViewHolder
|
||||
updateableApps.add(new UpdateableApp(activity, new App(cursor)));
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
|
||||
populateItems();
|
||||
notifyDataSetChanged();
|
||||
private void onKnownVulnLoadFinished(Cursor cursor) {
|
||||
knownVulnApps.clear();
|
||||
|
||||
cursor.moveToFirst();
|
||||
while (!cursor.isAfterLast()) {
|
||||
knownVulnApps.add(new KnownVulnApp(activity, new App(cursor)));
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,60 @@
|
||||
package org.fdroid.fdroid.views.updates.items;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.hannesdorfmann.adapterdelegates3.AdapterDelegate;
|
||||
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* List of all apps which can be updated, but have not yet been downloaded.
|
||||
*
|
||||
* @see KnownVulnApp The data that is bound to this view.
|
||||
* @see R.layout#known_vuln_app_list_item The view that this binds to.
|
||||
* @see KnownVulnAppListItemController Used for binding the {@link App} to
|
||||
* the {@link R.layout#known_vuln_app_list_item}
|
||||
*/
|
||||
public class KnownVulnApp extends AppUpdateData {
|
||||
|
||||
public final App app;
|
||||
|
||||
public KnownVulnApp(Activity activity, App app) {
|
||||
super(activity);
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public static class Delegate extends AdapterDelegate<List<AppUpdateData>> {
|
||||
|
||||
private final Activity activity;
|
||||
|
||||
public Delegate(Activity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isForViewType(@NonNull List<AppUpdateData> 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<AppUpdateData> items, int position,
|
||||
@NonNull RecyclerView.ViewHolder holder, @NonNull List<Object> payloads) {
|
||||
KnownVulnApp app = (KnownVulnApp) items.get(position);
|
||||
((KnownVulnAppListItemController) holder).bindModel(app.app);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
19
app/src/main/res/drawable/ic_known_vuln_overlay.xml
Normal file
19
app/src/main/res/drawable/ic_known_vuln_overlay.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<vector android:height="24dp" android:viewportHeight="12.7"
|
||||
android:viewportWidth="12.7" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillAlpha="1" android:fillColor="#ffffff"
|
||||
android:pathData="M6.35,6.35m-6.35,0a6.35,6.35 0,1 1,12.7 0a6.35,6.35 0,1 1,-12.7 0"
|
||||
android:strokeAlpha="1" android:strokeColor="#00000000"
|
||||
android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0.52916664"/>
|
||||
<path android:fillAlpha="1" android:fillColor="#ff3600"
|
||||
android:pathData="M6.35,6.35m-5.33,0a5.33,5.33 0,1 1,10.661 0a5.33,5.33 0,1 1,-10.661 0"
|
||||
android:strokeAlpha="1" android:strokeColor="#00000000"
|
||||
android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0.52916664"/>
|
||||
<path android:fillAlpha="1" android:fillColor="#ffffff"
|
||||
android:pathData="M5.863,3.778h0.904v3.082h-0.904z"
|
||||
android:strokeAlpha="1" android:strokeColor="#00000000"
|
||||
android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0.52916664"/>
|
||||
<path android:fillAlpha="1" android:fillColor="#ffffff"
|
||||
android:pathData="M5.863,8.899l0.904,0l0,-0.973l-0.904,0z"
|
||||
android:strokeAlpha="1" android:strokeColor="#00000000"
|
||||
android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0.52916664"/>
|
||||
</vector>
|
66
app/src/main/res/layout/known_vuln_app_list_item.xml
Normal file
66
app/src/main/res/layout/known_vuln_app_list_item.xml
Normal file
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="8dp"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<!-- Ignore ContentDescription because it is kind of meaningless to have TTS read out "App icon"
|
||||
when it will inevitably read out the name of the app straight after. -->
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
tools:src="@drawable/ic_launcher"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="48dp"
|
||||
android:layout_marginLeft="48dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:src="@drawable/ic_known_vuln_overlay"
|
||||
android:scaleType="fitCenter"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="We found a vulnerability with VulnApp. We recommend uninstalling this app immediately."
|
||||
android:textSize="16sp"
|
||||
android:textColor="?attr/installedApps"
|
||||
android:ellipsize="end"
|
||||
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginRight="8dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/action_button"
|
||||
style="@style/DetailsPrimaryButtonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/app_name"
|
||||
tools:text="Uninstall"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
@ -97,9 +97,10 @@ This often occurs with apps installed via Google Play or other sources, if they
|
||||
<string name="updates__tts__download_app">Download</string>
|
||||
<string name="updates__tts__download_updates_for_all_apps">Download all updates</string>
|
||||
|
||||
<string name="updates__app_with_known_vulnerability__uninstall">We found a vulnerability with %1$s. We recommend uninstalling this app immediately.</string>
|
||||
|
||||
<string name="updates__hide_updateable_apps">Hide apps</string>
|
||||
<string name="updates__show_updateable_apps">Show apps</string>
|
||||
|
||||
<plurals name="updates__download_updates_for_apps">
|
||||
<item quantity="one">Download update for %1$d app.</item>
|
||||
<item quantity="other">Download updates for %1$d apps.</item>
|
||||
|
@ -21,6 +21,7 @@ import org.fdroid.fdroid.data.ApkProvider;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.data.FDroidProviderTest;
|
||||
import org.fdroid.fdroid.data.InstalledAppTestUtils;
|
||||
import org.fdroid.fdroid.data.Repo;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.data.RepoPushRequest;
|
||||
@ -121,6 +122,12 @@ public class IndexV1UpdaterTest extends FDroidProviderTest {
|
||||
assertEquals("repo.mirrors should have items", 2, repo.mirrors.length);
|
||||
assertEquals("repo.mirrors first URL", "http://frkcchxlcvnb4m5a.onion/fdroid/repo", repo.mirrors[0]);
|
||||
assertEquals("repo.mirrors second URL", "http://testy.at.or.at/fdroid/repo", repo.mirrors[1]);
|
||||
|
||||
// Make sure the per-apk anti features which are new in index v1 get added correctly.
|
||||
assertEquals(0, AppProvider.Helper.findInstalledAppsWithKnownVulns(context).size());
|
||||
InstalledAppTestUtils.install(context, "com.waze", 1019841, "v3.9.5.4", "362488e7be5ea0689b4e97d989ae1404",
|
||||
"cbbdb8c5dafeccd7dd7b642dde0477d3489e18ac366e3c8473d5c07e5f735a95");
|
||||
assertEquals(1, AppProvider.Helper.findInstalledAppsWithKnownVulns(context).size());
|
||||
}
|
||||
|
||||
@Test(expected = RepoUpdater.SigningException.class)
|
||||
|
Loading…
x
Reference in New Issue
Block a user