show all installed apps as possibilities for panic uninstall

F-Droid should be able to uninstall any app, in theory, not just the apps
that are listed in the index.

This lays some groundwork for moving swap's SelectAppsView to the standard
AppList elements used everywhere else.  It also does a little bit towards
getting rid of InstalledApp in favor of just reusing App.
This commit is contained in:
Hans-Christoph Steiner 2019-06-11 22:56:04 +02:00
parent 89140d5334
commit bac0ae8f25
5 changed files with 102 additions and 12 deletions

View File

@ -40,8 +40,7 @@ import android.widget.TextView;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.Schema;
import org.fdroid.fdroid.data.InstalledAppProvider;
import org.fdroid.fdroid.views.installed.InstalledAppListAdapter;
public class SelectInstalledAppsActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {
@ -89,11 +88,7 @@ public class SelectInstalledAppsActivity extends AppCompatActivity implements Lo
@NonNull
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(
this,
AppProvider.getInstalledUri(),
Schema.AppMetadataTable.Cols.ALL,
null, null, null);
return new CursorLoader(this, InstalledAppProvider.getAllAppsUri(), null, null, null, null);
}
@Override

View File

@ -15,9 +15,12 @@ import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Schema.AppMetadataTable;
import org.fdroid.fdroid.data.Schema.InstalledAppTable;
import org.fdroid.fdroid.data.Schema.InstalledAppTable.Cols;
import org.fdroid.fdroid.data.Schema.PackageTable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@ -28,6 +31,23 @@ public class InstalledAppProvider extends FDroidProvider {
public static class Helper {
public static App[] all(Context context) {
ArrayList<App> appList = new ArrayList<>();
Cursor cursor = context.getContentResolver().query(InstalledAppProvider.getAllAppsUri(),
null, null, null, null);
if (cursor != null) {
if (cursor.getCount() > 0) {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
appList.add(new App(cursor));
cursor.moveToNext();
}
}
cursor.close();
}
return appList.toArray(new App[0]);
}
/**
* @return The keys are the package names, and their corresponding values are
* the {@link PackageInfo#lastUpdateTime last update time} in milliseconds.
@ -79,6 +99,8 @@ public class InstalledAppProvider extends FDroidProvider {
private static final String PATH_SEARCH = "search";
private static final int CODE_SEARCH = CODE_SINGLE + 1;
private static final String PATH_ALL_APPS = "allApps";
private static final int CODE_ALL_APPS = CODE_SEARCH + 1;
private static final UriMatcher MATCHER = new UriMatcher(-1);
@ -99,6 +121,7 @@ public class InstalledAppProvider extends FDroidProvider {
static {
MATCHER.addURI(getAuthority(), null, CODE_LIST);
MATCHER.addURI(getAuthority(), PATH_SEARCH + "/*", CODE_SEARCH);
MATCHER.addURI(getAuthority(), PATH_ALL_APPS, CODE_ALL_APPS);
MATCHER.addURI(getAuthority(), "*", CODE_SINGLE);
}
@ -106,6 +129,10 @@ public class InstalledAppProvider extends FDroidProvider {
return Uri.parse("content://" + getAuthority());
}
public static Uri getAllAppsUri() {
return getContentUri().buildUpon().appendPath(PATH_ALL_APPS).build();
}
/**
* @return the {@link Uri} that points to a specific installed app
*/
@ -228,6 +255,7 @@ public class InstalledAppProvider extends FDroidProvider {
}
QuerySelection selection = new QuerySelection(customSelection, selectionArgs);
QueryBuilder query = null;
switch (MATCHER.match(uri)) {
case CODE_LIST:
selection = selectNotSystemSignature(selection);
@ -241,16 +269,30 @@ public class InstalledAppProvider extends FDroidProvider {
selection = selection.add(querySearch(uri.getLastPathSegment()));
break;
case CODE_ALL_APPS:
selection = selectNotSystemSignature(selection);
query = new QueryBuilder();
query.addField(Cols._ID);
query.appendField(Cols.APPLICATION_LABEL, null, Schema.AppMetadataTable.Cols.NAME);
query.appendField(Cols.VERSION_CODE, null, AppMetadataTable.Cols.UPSTREAM_VERSION_CODE);
query.appendField(Cols.VERSION_NAME, null, AppMetadataTable.Cols.UPSTREAM_VERSION_NAME);
query.appendField(PackageTable.Cols.PACKAGE_NAME, PackageTable.NAME,
AppMetadataTable.Cols.Package.PACKAGE_NAME);
break;
default:
String message = "Invalid URI for installed app content provider: " + uri;
Log.e(TAG, message);
throw new UnsupportedOperationException(message);
}
QueryBuilder query = new QueryBuilder();
if (projection == null || projection.length == 0) {
if (query != null) { // NOPMD
// the fields are already setup above
} else if (projection == null || projection.length == 0) {
query = new QueryBuilder();
query.addFields(Cols.ALL);
} else {
query = new QueryBuilder();
query.addFields(projection);
}
query.addSelection(selection);
@ -279,6 +321,12 @@ public class InstalledAppProvider extends FDroidProvider {
return count;
}
/**
* {@link Cols.Package#NAME} is not included in the database here, because
* it is included only in the {@link PackageTable}, since there are large
* cross-table queries needed to handle the complexity of multiple repos
* potentially serving the same apps.
*/
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {

View File

@ -7,6 +7,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Outline;
import android.net.Uri;
import android.os.Build;
@ -186,7 +187,15 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
public void bindModel(@NonNull App app) {
currentApp = app;
ImageLoader.getInstance().displayImage(app.iconUrl, icon, Utils.getRepoAppDisplayImageOptions());
if (app.iconUrl == null) {
try {
icon.setImageDrawable(activity.getPackageManager().getApplicationIcon(app.packageName));
} catch (PackageManager.NameNotFoundException e) {
// ignored
}
} else {
ImageLoader.getInstance().displayImage(app.iconUrl, icon, Utils.getRepoAppDisplayImageOptions());
}
// Figures out the current install/update/download/etc status for the app we are viewing.
// Then, asks the view to update itself to reflect this status.

View File

@ -2,6 +2,7 @@ package org.fdroid.fdroid.views.installed;
import android.app.Activity;
import android.database.Cursor;
import android.provider.BaseColumns;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
@ -9,7 +10,6 @@ import android.view.View;
import android.view.ViewGroup;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.Schema;
public class InstalledAppListAdapter extends RecyclerView.Adapter<InstalledAppListItemController> {
@ -30,7 +30,8 @@ public class InstalledAppListAdapter extends RecyclerView.Adapter<InstalledAppLi
}
cursor.moveToPosition(position);
return cursor.getLong(cursor.getColumnIndex(Schema.AppMetadataTable.Cols.ROW_ID));
// TODO this should be based on Schema.InstalledAppProvider.Cols._ID
return cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
}
@NonNull

View File

@ -20,6 +20,7 @@ import java.util.Map;
import static org.fdroid.fdroid.Assert.assertIsInstalledVersionInDb;
import static org.fdroid.fdroid.Assert.assertResultCount;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@ -71,6 +72,39 @@ public class InstalledAppProviderTest extends FDroidProviderTest {
cursor.close();
}
@Test
public void testHelperAll() {
final String packageName0 = "com.0";
final String packageName1 = "com.1";
final String packageName2 = "com.2";
App[] apps = InstalledAppProvider.Helper.all(context);
assertEquals(0, apps.length);
insertInstalledApp(packageName0, 0, "v0");
insertInstalledApp(packageName1, 1, "v1");
insertInstalledApp(packageName2, 2, "v2");
assertResultCount(contentResolver, 3, InstalledAppProvider.getContentUri());
assertResultCount(contentResolver, 3, InstalledAppProvider.getAllAppsUri());
assertIsInstalledVersionInDb(contentResolver, packageName0, 0, "v0");
assertIsInstalledVersionInDb(contentResolver, packageName1, 1, "v1");
assertIsInstalledVersionInDb(contentResolver, packageName2, 2, "v2");
apps = InstalledAppProvider.Helper.all(context);
assertEquals(3, apps.length);
assertEquals(packageName0, apps[0].packageName);
assertEquals("v0", apps[0].upstreamVersionName);
assertEquals(0, apps[0].upstreamVersionCode);
assertEquals(packageName1, apps[1].packageName);
assertEquals("v1", apps[1].upstreamVersionName);
assertEquals(1, apps[1].upstreamVersionCode);
assertEquals(packageName2, apps[2].packageName);
assertEquals("v2", apps[2].upstreamVersionName);
assertEquals(2, apps[2].upstreamVersionCode);
assertNotEquals(packageName0, apps[2].packageName);
}
@Test
public void testInsert() {
@ -84,6 +118,9 @@ public class InstalledAppProviderTest extends FDroidProviderTest {
assertIsInstalledVersionInDb(contentResolver, "com.example.com1", 1, "v1");
assertIsInstalledVersionInDb(contentResolver, "com.example.com2", 2, "v2");
assertIsInstalledVersionInDb(contentResolver, "com.example.com3", 3, "v3");
App[] apps = InstalledAppProvider.Helper.all(context);
assertEquals(3, apps.length);
}
@Test