diff --git a/app/build.gradle b/app/build.gradle
index 47fa26d11..3269d24f3 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -148,7 +148,7 @@ dependencies {
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
implementation 'com.google.zxing:core:3.3.3'
implementation 'info.guardianproject.netcipher:netcipher:2.0.0-beta1'
- implementation 'info.guardianproject.panic:panic:0.5'
+ implementation 'info.guardianproject.panic:panic:1.0'
implementation 'commons-io:commons-io:2.6'
implementation 'commons-net:commons-net:3.6'
implementation 'ch.acra:acra:4.9.1'
diff --git a/app/src/full/AndroidManifest.xml b/app/src/full/AndroidManifest.xml
index a39ec62e8..7835b76a9 100644
--- a/app/src/full/AndroidManifest.xml
+++ b/app/src/full/AndroidManifest.xml
@@ -104,6 +104,10 @@
+
+
wipeSet = Preferences.get().getPanicWipeSet();
+ categoryAppsToUninstall.removeAll();
+ if (Panic.PACKAGE_NAME_NONE.equals(prefApp.getValue())) {
+ categoryAppsToUninstall.setEnabled(false);
+ return;
+ }
+ categoryAppsToUninstall.setEnabled(true);
+ if (wipeSet.size() > 0) {
+ for (String packageName : wipeSet) {
+ Preference preference = new DestructivePreference(getActivity());
+ preference.setSingleLineTitle(true);
+ preference.setIntent(intent);
+ categoryAppsToUninstall.addPreference(preference);
+ try {
+ preference.setTitle(pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)));
+ preference.setIcon(pm.getApplicationIcon(packageName));
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ preference.setTitle(packageName);
+ }
+ }
+ } else {
+ Preference preference = new Preference(getActivity());
+ preference.setIntent(intent);
+ Drawable icon = getResources().getDrawable(R.drawable.ic_add_circle_outline_white);
+ icon.setColorFilter(new LightingColorFilter(0, getResources().getColor(R.color.swap_light_grey_icon)));
+ preference.setSingleLineTitle(true);
+ preference.setTitle(R.string.panic_add_apps_to_uninstall);
+ preference.setIcon(icon);
+ categoryAppsToUninstall.addPreference(preference);
+ }
}
@Override
@@ -98,11 +149,13 @@ public class PanicPreferencesFragment extends PreferenceFragment
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- if (key.equals(PREF_HIDE) && sharedPreferences.getBoolean(PREF_HIDE, false)) {
+ if (key.equals(Preferences.PREF_PANIC_HIDE)
+ && sharedPreferences.getBoolean(Preferences.PREF_PANIC_HIDE, false)) {
showHideConfirmationDialog();
}
// disable "hiding" if "exit" gets disabled
- if (key.equals(PREF_EXIT) && !sharedPreferences.getBoolean(PREF_EXIT, true)) {
+ if (key.equals(Preferences.PREF_PANIC_EXIT)
+ && !sharedPreferences.getBoolean(Preferences.PREF_PANIC_EXIT, true)) {
prefHide.setChecked(false);
}
}
@@ -156,6 +209,7 @@ public class PanicPreferencesFragment extends PreferenceFragment
// disable destructive panic actions
prefHide.setEnabled(false);
+ showWipeList();
} else {
// try to display connected panic app
try {
@@ -163,6 +217,8 @@ public class PanicPreferencesFragment extends PreferenceFragment
prefApp.setSummary(pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)));
prefApp.setIcon(pm.getApplicationIcon(packageName));
prefHide.setEnabled(true);
+ prefResetRepos.setEnabled(true);
+ showWipeList();
} catch (PackageManager.NameNotFoundException e) {
// revert back to no app, just to be safe
PanicResponder.setTriggerPackageName(getActivity(), Panic.PACKAGE_NAME_NONE);
@@ -243,6 +299,7 @@ public class PanicPreferencesFragment extends PreferenceFragment
@Override
public void onCancel(DialogInterface dialogInterface) {
prefHide.setChecked(false);
+ prefResetRepos.setChecked(false);
}
});
builder.setView(R.layout.dialog_app_hiding);
diff --git a/app/src/full/java/org/fdroid/fdroid/views/panic/PanicResponderActivity.java b/app/src/full/java/org/fdroid/fdroid/views/panic/PanicResponderActivity.java
index d0c18e6ab..6f3c9d9c6 100644
--- a/app/src/full/java/org/fdroid/fdroid/views/panic/PanicResponderActivity.java
+++ b/app/src/full/java/org/fdroid/fdroid/views/panic/PanicResponderActivity.java
@@ -1,17 +1,48 @@
package org.fdroid.fdroid.views.panic;
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ContentValues;
+import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
+import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
-
-import org.fdroid.fdroid.Preferences;
-import org.fdroid.fdroid.views.hiding.HidingManager;
-
import info.guardianproject.panic.Panic;
import info.guardianproject.panic.PanicResponder;
+import org.fdroid.fdroid.Preferences;
+import org.fdroid.fdroid.data.Apk;
+import org.fdroid.fdroid.data.DBHelper;
+import org.fdroid.fdroid.data.InstalledApp;
+import org.fdroid.fdroid.data.InstalledAppProvider;
+import org.fdroid.fdroid.data.Repo;
+import org.fdroid.fdroid.data.RepoProvider;
+import org.fdroid.fdroid.data.Schema;
+import org.fdroid.fdroid.installer.Installer;
+import org.fdroid.fdroid.installer.InstallerService;
+import org.fdroid.fdroid.installer.PrivilegedInstaller;
+import org.fdroid.fdroid.views.hiding.HidingManager;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This {@link Activity} is purely to run events in response to a panic trigger.
+ * It needs to be an {@code Activity} rather than a {@link android.app.Service}
+ * so that it can fetch some of the required information about what sent the
+ * {@link Intent}. This is therefore an {@code Activity} without any UI, which
+ * is a special case in Android. All the code must be in
+ * {@link #onCreate(Bundle)} and {@link #finish()} must be called at the end of
+ * that method.
+ *
+ * @see PanicResponder#receivedTriggerFromConnectedApp(Activity)
+ */
public class PanicResponderActivity extends AppCompatActivity {
private static final String TAG = PanicResponderActivity.class.getSimpleName();
@@ -21,7 +52,7 @@ public class PanicResponderActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
- if (intent == null || !Panic.isTriggerIntent(intent)) {
+ if (!Panic.isTriggerIntent(intent)) {
finish();
return;
}
@@ -29,12 +60,71 @@ public class PanicResponderActivity extends AppCompatActivity {
// received intent from panic app
Log.i(TAG, "Received Panic Trigger...");
- Preferences preferences = Preferences.get();
+ final Preferences preferences = Preferences.get();
- if (PanicResponder.receivedTriggerFromConnectedApp(this)) {
- Log.i(TAG, "Panic Trigger came from connected app");
+ boolean receivedTriggerFromConnectedApp = PanicResponder.receivedTriggerFromConnectedApp(this);
+ final boolean runningAppUninstalls = PrivilegedInstaller.isDefault(this);
- // Performing destructive panic responses
+ ArrayList wipeList = new ArrayList<>(preferences.getPanicWipeSet());
+ preferences.setPanicWipeSet(Collections.emptySet());
+ preferences.setPanicTmpSelectedSet(Collections.emptySet());
+
+ if (receivedTriggerFromConnectedApp && runningAppUninstalls && wipeList.size() > 0) {
+
+ // if this app (e.g. F-Droid) is to be deleted, do it last
+ if (wipeList.contains(getPackageName())) {
+ wipeList.remove(getPackageName());
+ wipeList.add(getPackageName());
+ }
+
+ final Context context = this;
+ final CountDownLatch latch = new CountDownLatch(1);
+ final LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
+ final String lastToUninstall = wipeList.get(wipeList.size() - 1);
+ final BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch ((intent.getAction())) {
+ case Installer.ACTION_UNINSTALL_INTERRUPTED:
+ case Installer.ACTION_UNINSTALL_COMPLETE:
+ latch.countDown();
+ break;
+ }
+ }
+ };
+ lbm.registerReceiver(receiver, Installer.getUninstallIntentFilter(lastToUninstall));
+
+ for (String packageName : wipeList) {
+ InstalledApp installedApp = InstalledAppProvider.Helper.findByPackageName(context, packageName);
+ InstallerService.uninstall(context, new Apk(installedApp));
+ }
+
+ // wait for apps to uninstall before triggering final responses
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ latch.await(10, TimeUnit.MINUTES);
+ } catch (InterruptedException e) {
+ // ignored
+ }
+ lbm.unregisterReceiver(receiver);
+ if (preferences.panicResetRepos()) {
+ resetRepos(context);
+ }
+ if (preferences.panicHide()) {
+ HidingManager.hide(context);
+ }
+ if (preferences.panicExit()) {
+ exitAndClear();
+ }
+ }
+ }.start();
+ } else if (receivedTriggerFromConnectedApp) {
+ if (preferences.panicResetRepos()) {
+ resetRepos(this);
+ }
+ // Performing destructive panic response
if (preferences.panicHide()) {
Log.i(TAG, "Hiding app...");
HidingManager.hide(this);
@@ -42,13 +132,43 @@ public class PanicResponderActivity extends AppCompatActivity {
}
// exit and clear, if not deactivated
- if (preferences.panicExit()) {
- ExitActivity.exitAndRemoveFromRecentApps(this);
- if (Build.VERSION.SDK_INT >= 21) {
- finishAndRemoveTask();
- }
+ if (!runningAppUninstalls && preferences.panicExit()) {
+ exitAndClear();
}
finish();
}
+ static void resetRepos(Context context) {
+ HashSet enabledAddresses = new HashSet<>();
+ HashSet disabledAddresses = new HashSet<>();
+ String[] defaultReposItems = DBHelper.loadInitialRepos(context).toArray(new String[0]);
+ for (int i = 1; i < defaultReposItems.length; i += DBHelper.REPO_XML_ITEM_COUNT) {
+ if ("1".equals(defaultReposItems[i + 3])) {
+ enabledAddresses.add(defaultReposItems[i]);
+ } else {
+ disabledAddresses.add(defaultReposItems[i]);
+ }
+ }
+
+ List repos = RepoProvider.Helper.all(context);
+ for (Repo repo : repos) {
+ ContentValues values = new ContentValues(1);
+ if (enabledAddresses.contains(repo.address)) {
+ values.put(Schema.RepoTable.Cols.IN_USE, true);
+ RepoProvider.Helper.update(context, repo, values);
+ } else if (disabledAddresses.contains(repo.address)) {
+ values.put(Schema.RepoTable.Cols.IN_USE, false);
+ RepoProvider.Helper.update(context, repo, values);
+ } else {
+ RepoProvider.Helper.remove(context, repo.getId());
+ }
+ }
+ }
+
+ private void exitAndClear() {
+ ExitActivity.exitAndRemoveFromRecentApps(this);
+ if (Build.VERSION.SDK_INT >= 21) {
+ finishAndRemoveTask();
+ }
+ }
}
diff --git a/app/src/full/java/org/fdroid/fdroid/views/panic/SelectInstalledAppListAdapter.java b/app/src/full/java/org/fdroid/fdroid/views/panic/SelectInstalledAppListAdapter.java
new file mode 100644
index 000000000..e0c49c927
--- /dev/null
+++ b/app/src/full/java/org/fdroid/fdroid/views/panic/SelectInstalledAppListAdapter.java
@@ -0,0 +1,30 @@
+package org.fdroid.fdroid.views.panic;
+
+import android.app.Activity;
+import android.support.annotation.NonNull;
+import android.view.View;
+import android.view.ViewGroup;
+import org.fdroid.fdroid.Preferences;
+import org.fdroid.fdroid.R;
+import org.fdroid.fdroid.views.installed.InstalledAppListAdapter;
+import org.fdroid.fdroid.views.installed.InstalledAppListItemController;
+
+import java.util.Set;
+
+public class SelectInstalledAppListAdapter extends InstalledAppListAdapter {
+ private final Set selectedApps;
+
+ SelectInstalledAppListAdapter(Activity activity) {
+ super(activity);
+ Preferences prefs = Preferences.get();
+ selectedApps = prefs.getPanicWipeSet();
+ prefs.setPanicTmpSelectedSet(selectedApps);
+ }
+
+ @NonNull
+ @Override
+ public InstalledAppListItemController onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = activity.getLayoutInflater().inflate(R.layout.installed_app_list_item, parent, false);
+ return new SelectInstalledAppListItemController(activity, view, selectedApps);
+ }
+}
diff --git a/app/src/full/java/org/fdroid/fdroid/views/panic/SelectInstalledAppListItemController.java b/app/src/full/java/org/fdroid/fdroid/views/panic/SelectInstalledAppListItemController.java
new file mode 100644
index 000000000..eac2c9f2d
--- /dev/null
+++ b/app/src/full/java/org/fdroid/fdroid/views/panic/SelectInstalledAppListItemController.java
@@ -0,0 +1,37 @@
+package org.fdroid.fdroid.views.panic;
+
+import android.app.Activity;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.View;
+import org.fdroid.fdroid.AppUpdateStatusManager;
+import org.fdroid.fdroid.data.App;
+import org.fdroid.fdroid.views.apps.AppListItemState;
+import org.fdroid.fdroid.views.installed.InstalledAppListItemController;
+
+import java.util.Set;
+
+/**
+ * Shows the currently installed apps as a selectable list.
+ */
+public class SelectInstalledAppListItemController extends InstalledAppListItemController {
+
+ private final Set selectedApps;
+
+ public SelectInstalledAppListItemController(Activity activity, View itemView, Set selectedApps) {
+ super(activity, itemView);
+ this.selectedApps = selectedApps;
+ }
+
+ @NonNull
+ @Override
+ protected AppListItemState getCurrentViewState(
+ @NonNull App app, @Nullable AppUpdateStatusManager.AppUpdateStatus appStatus) {
+ return new AppListItemState(app).setCheckBoxStatus(selectedApps.contains(app.packageName));
+ }
+
+ @Override
+ protected void onActionButtonPressed(App app) {
+ super.onActionButtonPressed(app);
+ }
+}
diff --git a/app/src/full/java/org/fdroid/fdroid/views/panic/SelectInstalledAppsActivity.java b/app/src/full/java/org/fdroid/fdroid/views/panic/SelectInstalledAppsActivity.java
new file mode 100644
index 000000000..73ba4db50
--- /dev/null
+++ b/app/src/full/java/org/fdroid/fdroid/views/panic/SelectInstalledAppsActivity.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2010-12 Ciaran Gultnieks, ciaran@ciarang.com
+ * Copyright (C) 2009 Roberto Jacinto, roberto.jacinto@caixamagica.pt
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.fdroid.fdroid.views.panic;
+
+import android.annotation.SuppressLint;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.LightingColorFilter;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.Toolbar;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.TextView;
+import org.fdroid.fdroid.FDroidApp;
+import org.fdroid.fdroid.Preferences;
+import org.fdroid.fdroid.R;
+import org.fdroid.fdroid.data.InstalledAppProvider;
+import org.fdroid.fdroid.views.installed.InstalledAppListAdapter;
+
+public class SelectInstalledAppsActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks {
+
+ private InstalledAppListAdapter adapter;
+ private RecyclerView appList;
+ private TextView emptyState;
+ private int checkId;
+
+ private Preferences prefs;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+
+ ((FDroidApp) getApplication()).applyTheme(this);
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.installed_apps_layout);
+
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ toolbar.setTitle(getString(R.string.panic_add_apps_to_uninstall));
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ adapter = new SelectInstalledAppListAdapter(this);
+
+ appList = findViewById(R.id.app_list);
+ appList.setHasFixedSize(true);
+ appList.setLayoutManager(new LinearLayoutManager(this));
+ appList.setAdapter(adapter);
+
+ emptyState = findViewById(R.id.empty_state);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ prefs = Preferences.get();
+
+ // Starts a new or restarts an existing Loader in this manager
+ getSupportLoaderManager().restartLoader(0, null, this);
+ }
+
+ @NonNull
+ @Override
+ public Loader onCreateLoader(int id, Bundle args) {
+ return new CursorLoader(this, InstalledAppProvider.getAllAppsUri(), null, null, null, null);
+ }
+
+ @Override
+ public void onLoadFinished(@NonNull Loader loader, Cursor cursor) {
+ adapter.setApps(cursor);
+
+ if (adapter.getItemCount() == 0) {
+ appList.setVisibility(View.GONE);
+ emptyState.setVisibility(View.VISIBLE);
+ } else {
+ appList.setVisibility(View.VISIBLE);
+ emptyState.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(@NonNull Loader loader) {
+ adapter.setApps(null);
+ }
+
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuItem menuItem = menu.add(R.string.menu_select_for_wipe);
+ menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+ checkId = menuItem.getItemId();
+ if (FDroidApp.isAppThemeLight()) {
+ Resources resources = getResources();
+ Drawable icon = resources.getDrawable(R.drawable.check);
+ icon.setColorFilter(new LightingColorFilter(0xffffffff, resources.getColor(android.R.color.white)));
+ menuItem.setIcon(icon);
+ } else {
+ menuItem.setIcon(R.drawable.check);
+ }
+ return true;
+ }
+
+ @SuppressLint("ApplySharedPref")
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ finish();
+ if (item.getItemId() == checkId) {
+ prefs.setPanicWipeSet(prefs.getPanicTmpSelectedSet());
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/SelectAppsView.java b/app/src/full/java/org/fdroid/fdroid/views/swap/SelectAppsView.java
index d45ffdb5f..38ecf5ba9 100644
--- a/app/src/full/java/org/fdroid/fdroid/views/swap/SelectAppsView.java
+++ b/app/src/full/java/org/fdroid/fdroid/views/swap/SelectAppsView.java
@@ -30,7 +30,6 @@ import org.fdroid.fdroid.R;
import org.fdroid.fdroid.data.InstalledAppProvider;
import org.fdroid.fdroid.data.Schema.InstalledAppTable;
import org.fdroid.fdroid.localrepo.LocalRepoService;
-import org.fdroid.fdroid.localrepo.SwapService;
import org.fdroid.fdroid.localrepo.SwapView;
public class SelectAppsView extends SwapView implements LoaderManager.LoaderCallbacks {
@@ -61,7 +60,7 @@ public class SelectAppsView extends SwapView implements LoaderManager.LoaderCall
listView = findViewById(R.id.list);
adapter = new AppListAdapter(listView, getContext(),
getContext().getContentResolver().query(InstalledAppProvider.getContentUri(),
- InstalledAppTable.Cols.ALL, null, null, null));
+ null, null, null, null));
listView.setAdapter(adapter);
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
@@ -97,13 +96,7 @@ public class SelectAppsView extends SwapView implements LoaderManager.LoaderCall
} else {
uri = InstalledAppProvider.getSearchUri(currentFilterString);
}
- return new CursorLoader(
- getActivity(),
- uri,
- InstalledAppTable.Cols.ALL,
- null,
- null,
- InstalledAppTable.Cols.APPLICATION_LABEL);
+ return new CursorLoader(getActivity(), uri, null, null, null, null);
}
@Override
diff --git a/app/src/full/res/drawable/check.xml b/app/src/full/res/drawable/check.xml
new file mode 100644
index 000000000..064f846ae
--- /dev/null
+++ b/app/src/full/res/drawable/check.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/full/res/values/colors.xml b/app/src/full/res/values/colors.xml
index fe1fee70e..b7793017e 100644
--- a/app/src/full/res/values/colors.xml
+++ b/app/src/full/res/values/colors.xml
@@ -1,5 +1,5 @@
-
+
#ff7900
#27aae1
@@ -10,4 +10,6 @@
#4a4a4a
#bbbbbb
+ #fff0f0
+ #330000
diff --git a/app/src/full/res/xml/preferences_panic.xml b/app/src/full/res/xml/preferences_panic.xml
index e26d3eabb..e512111be 100644
--- a/app/src/full/res/xml/preferences_panic.xml
+++ b/app/src/full/res/xml/preferences_panic.xml
@@ -19,13 +19,26 @@
android:title="@string/panic_app_setting_title"
tools:icon="@drawable/ic_cancel"/>
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/org/fdroid/fdroid/Preferences.java b/app/src/main/java/org/fdroid/fdroid/Preferences.java
index 46dc29b3b..d6f455453 100644
--- a/app/src/main/java/org/fdroid/fdroid/Preferences.java
+++ b/app/src/main/java/org/fdroid/fdroid/Preferences.java
@@ -35,10 +35,12 @@ import org.fdroid.fdroid.installer.PrivilegedInstaller;
import org.fdroid.fdroid.net.ConnectivityMonitorService;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
@@ -107,6 +109,9 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
public static final String PREF_PREVENT_SCREENSHOTS = "preventScreenshots";
public static final String PREF_PANIC_EXIT = "pref_panic_exit";
public static final String PREF_PANIC_HIDE = "pref_panic_hide";
+ public static final String PREF_PANIC_RESET_REPOS = "pref_panic_reset_repos";
+ public static final String PREF_PANIC_WIPE_SET = "panicWipeSet";
+ public static final String PREF_PANIC_TMP_SELECTED_SET = "panicTmpSelectedSet";
public static final String PREF_HIDE_ON_LONG_PRESS_SEARCH = "hideOnLongPressSearch";
public static final String PREF_HIDE_ALL_NOTIFICATIONS = "hideAllNotifications";
public static final String PREF_SEND_VERSION_AND_UUID_TO_SERVERS = "sendVersionAndUUIDToServers";
@@ -511,10 +516,30 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
return preferences.getBoolean(PREF_PANIC_HIDE, IGNORED_B);
}
+ public boolean panicResetRepos() {
+ return preferences.getBoolean(PREF_PANIC_RESET_REPOS, IGNORED_B);
+ }
+
public boolean hideOnLongPressSearch() {
return preferences.getBoolean(PREF_HIDE_ON_LONG_PRESS_SEARCH, IGNORED_B);
}
+ public Set getPanicTmpSelectedSet() {
+ return preferences.getStringSet(Preferences.PREF_PANIC_TMP_SELECTED_SET, Collections.emptySet());
+ }
+
+ public void setPanicTmpSelectedSet(Set selectedSet) {
+ preferences.edit().putStringSet(Preferences.PREF_PANIC_TMP_SELECTED_SET, selectedSet).apply();
+ }
+
+ public Set getPanicWipeSet() {
+ return preferences.getStringSet(Preferences.PREF_PANIC_WIPE_SET, Collections.emptySet());
+ }
+
+ public void setPanicWipeSet(Set selectedSet) {
+ preferences.edit().putStringSet(Preferences.PREF_PANIC_WIPE_SET, selectedSet).apply();
+ }
+
/**
* Preference for whitelabel builds that are meant to be entirely controlled
* by the server, without user interaction, e.g. "appliances".
diff --git a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java
index 00e6645fe..4a6135a59 100644
--- a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java
+++ b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java
@@ -86,25 +86,38 @@ public class ApkProvider extends FDroidProvider {
* If installed, limit to apks signed by the same signer as the installed apk.
* Otherwise, limit to apks signed by the "preferred" signer (see {@link App#preferredSigner}).
*
+ * If all else fails, try to return some {@link Apk} that will install something,
+ * rather than returning a null and triggering a {@link NullPointerException}.
*/
+ @Nullable
public static Apk findSuggestedApk(Context context, App app) {
- return findApkFromAnyRepo(context, app.packageName, app.suggestedVersionCode,
- app.getMostAppropriateSignature());
+ String mostAppropriateSignature = app.getMostAppropriateSignature();
+ Apk apk = findApkFromAnyRepo(context, app.packageName, app.suggestedVersionCode,
+ mostAppropriateSignature);
+ if (apk == null && (mostAppropriateSignature == null || !app.isInstalled(context))) {
+ List apks = findByPackageName(context, app.packageName);
+ for (Apk availableApk : apks) {
+ if (availableApk.sig.equals(mostAppropriateSignature)) {
+ apk = availableApk;
+ break;
+ }
+ }
+ if (apk == null && apks.size() > 0) {
+ apk = apks.get(0);
+ }
+ }
+ return apk;
+
}
public static Apk findApkFromAnyRepo(Context context, String packageName, int versionCode) {
- return findApkFromAnyRepo(context, packageName, versionCode, null, Cols.ALL);
+ return findApkFromAnyRepo(context, packageName, versionCode, null);
}
public static Apk findApkFromAnyRepo(Context context, String packageName, int versionCode,
String signature) {
- return findApkFromAnyRepo(context, packageName, versionCode, signature, Cols.ALL);
- }
-
- public static Apk findApkFromAnyRepo(Context context, String packageName, int versionCode,
- @Nullable String signature, String[] projection) {
final Uri uri = getApkFromAnyRepoUri(packageName, versionCode, signature);
- return findByUri(context, uri, projection);
+ return findByUri(context, uri, Cols.ALL);
}
public static Apk findByUri(Context context, Uri uri, String[] projection) {
diff --git a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProvider.java
index 52d2e49b5..5790a3117 100644
--- a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProvider.java
+++ b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProvider.java
@@ -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,17 +31,33 @@ public class InstalledAppProvider extends FDroidProvider {
public static class Helper {
+ public static App[] all(Context context) {
+ ArrayList 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.
*/
- public static Map all(Context context) {
+ public static Map lastUpdateTimes(Context context) {
Map cachedInfo = new HashMap<>();
final Uri uri = InstalledAppProvider.getContentUri();
- final String[] projection = Cols.ALL;
- Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
+ Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
if (cursor != null) {
if (cursor.getCount() > 0) {
cursor.moveToFirst();
@@ -58,7 +77,7 @@ public class InstalledAppProvider extends FDroidProvider {
@Nullable
public static InstalledApp findByPackageName(Context context, String packageName) {
- Cursor cursor = context.getContentResolver().query(getAppUri(packageName), Cols.ALL, null, null, null);
+ Cursor cursor = context.getContentResolver().query(getAppUri(packageName), null, null, null, null);
if (cursor == null) {
return null;
}
@@ -80,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);
@@ -100,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);
}
@@ -107,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
*/
@@ -229,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);
@@ -242,16 +269,31 @@ 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();
- query.addFields(projection);
- if (projection.length == 0) {
- query.addField(Cols._ID);
+ 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);
query.addOrderBy(sortOrder);
@@ -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) {
diff --git a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java
index e827edca7..b001f97a7 100644
--- a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java
+++ b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java
@@ -176,7 +176,7 @@ public class InstalledAppProviderService extends JobIntentService {
*/
public static void compareToPackageManager(Context context) {
Utils.debugLog(TAG, "Comparing package manager to our installed app cache.");
- Map cachedInfo = InstalledAppProvider.Helper.all(context);
+ Map cachedInfo = InstalledAppProvider.Helper.lastUpdateTimes(context);
List packageInfoList = context.getPackageManager()
.getInstalledPackages(PackageManager.GET_SIGNATURES);
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 ad434b619..0b87fc5c7 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
@@ -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;
@@ -22,15 +23,15 @@ import android.text.TextUtils;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.Button;
+import android.widget.CheckBox;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
-
import com.nostra13.universalimageloader.core.ImageLoader;
-
import org.fdroid.fdroid.AppUpdateStatusManager;
import org.fdroid.fdroid.AppUpdateStatusManager.AppUpdateStatus;
+import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Apk;
@@ -45,6 +46,7 @@ import org.fdroid.fdroid.views.updates.UpdatesAdapter;
import java.io.File;
import java.util.Iterator;
+import java.util.Set;
/**
* Supports the following layouts:
@@ -64,6 +66,8 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
private static final String TAG = "AppListItemController";
+ private static Preferences prefs;
+
protected final Activity activity;
@NonNull
@@ -97,6 +101,9 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
@Nullable
private final Button secondaryButton;
+ @Nullable
+ private final CheckBox checkBox;
+
@Nullable
private App currentApp;
@@ -107,6 +114,9 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
public AppListItemController(final Activity activity, View itemView) {
super(itemView);
this.activity = activity;
+ if (prefs == null) {
+ prefs = Preferences.get();
+ }
installButton = (ImageView) itemView.findViewById(R.id.install);
if (installButton != null) {
@@ -145,6 +155,7 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
cancelButton = (ImageButton) itemView.findViewById(R.id.cancel_button);
actionButton = (Button) itemView.findViewById(R.id.action_button);
secondaryButton = (Button) itemView.findViewById(R.id.secondary_button);
+ checkBox = itemView.findViewById(R.id.checkbox);
if (actionButton != null) {
actionButton.setEnabled(true);
@@ -176,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.
@@ -220,9 +239,9 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
/**
* Override to respond to the user swiping an app to dismiss it from the list.
- * @param app The app that was swiped away
- * @param updatesAdapter The adapter. Can be used for refreshing the adapter with adapter.refreshStatuses().
*
+ * @param app The app that was swiped away
+ * @param updatesAdapter The adapter. Can be used for refreshing the adapter with adapter.refreshStatuses().
* @see #canDismiss() This must also be overridden and should return true.
*/
protected void onDismissApp(@NonNull App app, UpdatesAdapter updatesAdapter) {
@@ -328,6 +347,18 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
secondaryStatus.setText(statusText);
}
}
+
+ if (checkBox != null) {
+ if (viewState.shouldShowCheckBox()) {
+ itemView.setOnClickListener(selectInstalledAppListener);
+ checkBox.setChecked(viewState.isCheckBoxChecked());
+ checkBox.setVisibility(View.VISIBLE);
+ status.setVisibility(View.GONE);
+ secondaryStatus.setVisibility(View.GONE);
+ } else {
+ checkBox.setVisibility(View.GONE);
+ }
+ }
}
@NonNull
@@ -533,4 +564,18 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
InstallManagerService.cancel(activity, currentStatus.getCanonicalUrl());
}
+
+ private final View.OnClickListener selectInstalledAppListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Set wipeSet = prefs.getPanicTmpSelectedSet();
+ checkBox.toggle();
+ if (checkBox.isChecked()) {
+ wipeSet.add(currentApp.packageName);
+ } else {
+ wipeSet.remove(currentApp.packageName);
+ }
+ prefs.setPanicTmpSelectedSet(wipeSet);
+ }
+ };
}
diff --git a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemState.java b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemState.java
index 2fef4de81..464626fe2 100644
--- a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemState.java
+++ b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemState.java
@@ -2,12 +2,12 @@ package org.fdroid.fdroid.views.apps;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.App;
/**
* A dumb model which is used to specify what should/should not be shown in an {@link AppListItemController}.
+ *
* @see AppListItemController and its subclasses.
*/
public class AppListItemState {
@@ -20,6 +20,8 @@ public class AppListItemState {
private int progressCurrent = -1;
private int progressMax = -1;
private boolean showInstallButton;
+ private boolean showCheckBox;
+ private boolean isCheckBoxChecked;
public AppListItemState(@NonNull App app) {
this.app = app;
@@ -63,6 +65,9 @@ public class AppListItemState {
@Nullable
public CharSequence getMainText() {
+ if (showCheckBox) {
+ return app.name;
+ }
return mainText != null
? mainText
: Utils.formatAppNameAndSummary(app.name, app.summary);
@@ -113,4 +118,23 @@ public class AppListItemState {
public CharSequence getSecondaryStatusText() {
return secondaryStatusText;
}
+
+ public boolean shouldShowCheckBox() {
+ return showCheckBox;
+ }
+
+ public boolean isCheckBoxChecked() {
+ return isCheckBoxChecked;
+ }
+
+ /**
+ * Enable the {@link android.widget.CheckBox} display and set the on/off status
+ * e.g. {@link android.widget.CheckBox#isChecked()}
+ */
+ public AppListItemState setCheckBoxStatus(boolean checked) {
+ this.showCheckBox = true;
+ this.isCheckBoxChecked = checked;
+ return this;
+ }
+
}
diff --git a/app/src/main/java/org/fdroid/fdroid/views/installed/InstalledAppListAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/installed/InstalledAppListAdapter.java
index 0667afb96..57976a62b 100644
--- a/app/src/main/java/org/fdroid/fdroid/views/installed/InstalledAppListAdapter.java
+++ b/app/src/main/java/org/fdroid/fdroid/views/installed/InstalledAppListAdapter.java
@@ -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,16 +10,15 @@ 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;
-class InstalledAppListAdapter extends RecyclerView.Adapter {
+public class InstalledAppListAdapter extends RecyclerView.Adapter {
- private final Activity activity;
+ protected final Activity activity;
@Nullable
private Cursor cursor;
- InstalledAppListAdapter(Activity activity) {
+ protected InstalledAppListAdapter(Activity activity) {
this.activity = activity;
setHasStableIds(true);
}
@@ -30,7 +30,8 @@ class InstalledAppListAdapter extends RecyclerView.Adapter
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4c32706a1..538b62c0b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -206,6 +206,7 @@ This often occurs with apps installed via Google Play or other sources, if they
Install
Uninstall
+ Select for wipe
Ignore All Updates
Ignore This Update
Website
@@ -323,13 +324,18 @@ This often occurs with apps installed via Google Play or other sources, if they
Panic button settings
Actions to be taken in case of emergency
- Exit App
- App will be closed
+ Exit app
+ This app will be closed
Destructive Actions
+ Will be uninstalled and all data deleted
+ Apps to be uninstalled and all data wiped
+ Add apps to be uninstalled and wiped
Hide %s
App will hide itself
Remember how to restore
In a panic event, this will remove %1$s from the launcher. Only typing \"%2$d\" in the fake %3$s app can restore it.
+ Reset repos
+ Force the repo setup back to defaults
Calculator
diff --git a/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java
index e063e409b..7a62f82d1 100644
--- a/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java
+++ b/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java
@@ -392,23 +392,6 @@ public class ApkProviderTest extends FDroidProviderTest {
assertEquals("xxxxyyyy", apk.hash);
assertEquals("a hash type", apk.hashType);
- String[] projection = {
- Cols.Package.PACKAGE_NAME,
- Cols.HASH,
- };
-
- Apk apkLessFields = ApkProvider.Helper.findApkFromAnyRepo(context, "com.example", 11, null, projection);
-
- assertNotNull(apkLessFields);
-
- assertEquals("com.example", apkLessFields.packageName);
- assertEquals("xxxxyyyy", apkLessFields.hash);
-
- // Didn't ask for these fields, so should be their default values...
- assertNull(apkLessFields.hashType);
- assertNull(apkLessFields.versionName);
- assertEquals(0, apkLessFields.versionCode);
-
Apk notFound = ApkProvider.Helper.findApkFromAnyRepo(context, "com.doesnt.exist", 1000);
assertNull(notFound);
}
diff --git a/app/src/test/java/org/fdroid/fdroid/data/FDroidProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/FDroidProviderTest.java
index 416f564c5..54c3b5f9e 100644
--- a/app/src/test/java/org/fdroid/fdroid/data/FDroidProviderTest.java
+++ b/app/src/test/java/org/fdroid/fdroid/data/FDroidProviderTest.java
@@ -1,5 +1,6 @@
package org.fdroid.fdroid.data;
+import android.content.ContentValues;
import android.content.ContextWrapper;
import org.fdroid.fdroid.TestUtils;
import org.junit.After;
@@ -26,4 +27,10 @@ public abstract class FDroidProviderTest {
DBHelper.clearDbHelperSingleton();
}
+ protected Repo setEnabled(Repo repo, boolean enabled) {
+ ContentValues enable = new ContentValues(1);
+ enable.put(Schema.RepoTable.Cols.IN_USE, enabled);
+ RepoProvider.Helper.update(context, repo, enable);
+ return RepoProvider.Helper.findByAddress(context, repo.address);
+ }
}
diff --git a/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java
index 616677fd4..5eaee3f27 100644
--- a/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java
+++ b/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java
@@ -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;
@@ -35,7 +36,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest {
@Test
public void insertSingleApp() {
- Map foundBefore = InstalledAppProvider.Helper.all(RuntimeEnvironment.application);
+ Map foundBefore = InstalledAppProvider.Helper.lastUpdateTimes(RuntimeEnvironment.application);
assertEquals(foundBefore.size(), 0);
ContentValues values = new ContentValues();
@@ -49,7 +50,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest {
values.put(Cols.SIGNATURE, "000111222333444555666777888999aaabbbcccdddeeefff");
contentResolver.insert(InstalledAppProvider.getContentUri(), values);
- Map foundAfter = InstalledAppProvider.Helper.all(RuntimeEnvironment.application);
+ Map foundAfter = InstalledAppProvider.Helper.lastUpdateTimes(RuntimeEnvironment.application);
assertEquals(1, foundAfter.size());
assertEquals(100000000L, foundAfter.get("org.example.test-app").longValue());
@@ -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
diff --git a/app/src/test/java/org/fdroid/fdroid/data/RepoProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/RepoProviderTest.java
index de6326e4f..a77624248 100644
--- a/app/src/test/java/org/fdroid/fdroid/data/RepoProviderTest.java
+++ b/app/src/test/java/org/fdroid/fdroid/data/RepoProviderTest.java
@@ -77,13 +77,6 @@ public class RepoProviderTest extends FDroidProviderTest {
assertEquals(0, RepoProvider.Helper.countEnabledRepos(context));
}
- private Repo setEnabled(Repo repo, boolean enabled) {
- ContentValues enable = new ContentValues(1);
- enable.put(RepoTable.Cols.IN_USE, enabled);
- RepoProvider.Helper.update(context, repo, enable);
- return RepoProvider.Helper.findByAddress(context, repo.address);
- }
-
@Test
public void lastUpdated() {
assertNull(RepoProvider.Helper.lastUpdate(context));
diff --git a/app/src/test/java/org/fdroid/fdroid/updater/AcceptableMultiIndexUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/updater/AcceptableMultiIndexUpdaterTest.java
index cbec38da4..48b8c22c3 100644
--- a/app/src/test/java/org/fdroid/fdroid/updater/AcceptableMultiIndexUpdaterTest.java
+++ b/app/src/test/java/org/fdroid/fdroid/updater/AcceptableMultiIndexUpdaterTest.java
@@ -1,4 +1,3 @@
-
package org.fdroid.fdroid.updater;
import android.content.ContentValues;
diff --git a/app/src/test/java/org/fdroid/fdroid/updater/AppIconsTest.java b/app/src/test/java/org/fdroid/fdroid/updater/AppIconsTest.java
index e659549a6..195a1c262 100644
--- a/app/src/test/java/org/fdroid/fdroid/updater/AppIconsTest.java
+++ b/app/src/test/java/org/fdroid/fdroid/updater/AppIconsTest.java
@@ -66,7 +66,7 @@ public class AppIconsTest extends MultiIndexUpdaterTest {
private void assertIconUrl(String expectedUrl) {
App app = AppProvider.Helper.findHighestPriorityMetadata(context.getContentResolver(),
- "org.adaway", new String[] {Schema.AppMetadataTable.Cols.ICON_URL});
+ "org.adaway", new String[]{Schema.AppMetadataTable.Cols.ICON_URL});
assertEquals(app.iconUrl, expectedUrl);
}
diff --git a/app/src/test/java/org/fdroid/fdroid/updater/FDroidRepoUpdateTest.java b/app/src/test/java/org/fdroid/fdroid/updater/FDroidRepoUpdateTest.java
index 898f6d7fb..a332f4daf 100644
--- a/app/src/test/java/org/fdroid/fdroid/updater/FDroidRepoUpdateTest.java
+++ b/app/src/test/java/org/fdroid/fdroid/updater/FDroidRepoUpdateTest.java
@@ -1,4 +1,3 @@
-
package org.fdroid.fdroid.updater;
import org.fdroid.fdroid.BuildConfig;
diff --git a/app/src/test/java/org/fdroid/fdroid/updater/Issue763MultiRepo.java b/app/src/test/java/org/fdroid/fdroid/updater/Issue763MultiRepo.java
index 606c5b6e6..277d521ca 100644
--- a/app/src/test/java/org/fdroid/fdroid/updater/Issue763MultiRepo.java
+++ b/app/src/test/java/org/fdroid/fdroid/updater/Issue763MultiRepo.java
@@ -1,6 +1,5 @@
package org.fdroid.fdroid.updater;
-import android.content.ContentValues;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.IndexUpdater;
import org.fdroid.fdroid.data.Apk;
@@ -35,12 +34,6 @@ public class Issue763MultiRepo extends MultiIndexUpdaterTest {
antoxRepo = createRepo("Tox", "https://pkg.tox.chat/fdroid/repo", context, antoxCert);
}
- public void setEnabled(Repo repo, boolean enabled) {
- ContentValues values = new ContentValues(1);
- values.put(Schema.RepoTable.Cols.IN_USE, enabled ? 1 : 0);
- RepoProvider.Helper.update(context, repo, values);
- }
-
@Test
public void antoxRepo() throws IndexUpdater.UpdateException {
assertAntoxEmpty();
@@ -111,7 +104,7 @@ public class Issue763MultiRepo extends MultiIndexUpdaterTest {
private void assertAntoxExists() {
String packageName = "chat.tox.antox";
List actualApksAfterUpdate = ApkProvider.Helper.findByRepo(context, antoxRepo, Schema.ApkTable.Cols.ALL);
- int[] expectedVersions = new int[] {15421};
+ int[] expectedVersions = new int[]{15421};
assertApp(packageName, expectedVersions);
assertApksExist(actualApksAfterUpdate, packageName, expectedVersions);
@@ -121,17 +114,17 @@ public class Issue763MultiRepo extends MultiIndexUpdaterTest {
List actualApksAfterUpdate = ApkProvider.Helper.findByRepo(context, microGRepo, Schema.ApkTable.Cols.ALL);
String vendingPackage = "com.android.vending";
- int[] expectedVendingVersions = new int[] {1};
+ int[] expectedVendingVersions = new int[]{1};
assertApp(vendingPackage, expectedVendingVersions);
assertApksExist(actualApksAfterUpdate, vendingPackage, expectedVendingVersions);
String gmsPackage = "com.google.android.gms";
- int[] expectedGmsVersions = new int[] {11059462, 10545451, 10545440, 10087438, 10087435, 9258259, 8492252, };
+ int[] expectedGmsVersions = new int[]{11059462, 10545451, 10545440, 10087438, 10087435, 9258259, 8492252};
assertApp(gmsPackage, expectedGmsVersions);
assertApksExist(actualApksAfterUpdate, gmsPackage, expectedGmsVersions);
String gsfPackage = "com.google.android.gsf";
- int[] expectedGsfVersions = new int[] {8};
+ int[] expectedGsfVersions = new int[]{8};
assertApp(gsfPackage, expectedGsfVersions);
assertApksExist(actualApksAfterUpdate, gsfPackage, expectedGsfVersions);
}
diff --git a/app/src/test/java/org/fdroid/fdroid/updater/MultiIndexUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/updater/MultiIndexUpdaterTest.java
index 2742dec5e..60f0cf9c4 100644
--- a/app/src/test/java/org/fdroid/fdroid/updater/MultiIndexUpdaterTest.java
+++ b/app/src/test/java/org/fdroid/fdroid/updater/MultiIndexUpdaterTest.java
@@ -1,14 +1,13 @@
-
package org.fdroid.fdroid.updater;
import android.content.ContentValues;
import android.content.Context;
import android.support.annotation.NonNull;
import android.text.TextUtils;
-import org.fdroid.fdroid.IndexV1Updater;
-import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.IndexUpdater;
import org.fdroid.fdroid.IndexUpdater.UpdateException;
+import org.fdroid.fdroid.IndexV1Updater;
+import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
diff --git a/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiIndexUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiIndexUpdaterTest.java
index 36b16d373..eab73ed39 100644
--- a/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiIndexUpdaterTest.java
+++ b/app/src/test/java/org/fdroid/fdroid/updater/ProperMultiIndexUpdaterTest.java
@@ -1,4 +1,3 @@
-
package org.fdroid.fdroid.updater;
import android.content.ContentValues;
@@ -41,7 +40,8 @@ public class ProperMultiIndexUpdaterTest extends MultiIndexUpdaterTest {
@Retention(RetentionPolicy.SOURCE)
@StringDef({"Conflicting", "Normal"})
- public @interface RepoIdentifier { }
+ public @interface RepoIdentifier {
+ }
@Test
public void appsRemovedFromRepo() throws IndexUpdater.UpdateException {
@@ -310,7 +310,9 @@ public class ProperMultiIndexUpdaterTest extends MultiIndexUpdaterTest {
assertAdAwayMetadata(adaway, id);
}
- /** @see ProperMultiIndexUpdaterTest#assert2048Metadata(Repo, String) */
+ /**
+ * @see ProperMultiIndexUpdaterTest#assert2048Metadata(Repo, String)
+ */
private void assertAdAwayMetadata(App adaway, @RepoIdentifier String id) {
assertNotNull(adaway);
assertEquals(String.format("AdAway", id),
@@ -338,7 +340,9 @@ public class ProperMultiIndexUpdaterTest extends MultiIndexUpdaterTest {
assertAdbMetadata(adb, id);
}
- /** @see ProperMultiIndexUpdaterTest#assert2048Metadata(Repo, String) */
+ /**
+ * @see ProperMultiIndexUpdaterTest#assert2048Metadata(Repo, String)
+ */
private void assertAdbMetadata(App adb, @RepoIdentifier String id) {
assertNotNull(adb);
assertEquals("adbWireless", adb.name);
@@ -355,7 +359,9 @@ public class ProperMultiIndexUpdaterTest extends MultiIndexUpdaterTest {
assertCalendarMetadata(calendar, id);
}
- /** @see ProperMultiIndexUpdaterTest#assert2048Metadata(Repo, String) */
+ /**
+ * @see ProperMultiIndexUpdaterTest#assert2048Metadata(Repo, String)
+ */
private void assertCalendarMetadata(App calendar, @RepoIdentifier String id) {
assertNotNull(calendar);
assertEquals("Add to calendar",
diff --git a/app/src/testFull/java/org/fdroid/fdroid/views/panic/PanicResponderActivityTest.java b/app/src/testFull/java/org/fdroid/fdroid/views/panic/PanicResponderActivityTest.java
new file mode 100644
index 000000000..6c4fdca82
--- /dev/null
+++ b/app/src/testFull/java/org/fdroid/fdroid/views/panic/PanicResponderActivityTest.java
@@ -0,0 +1,53 @@
+package org.fdroid.fdroid.views.panic;
+
+import org.fdroid.fdroid.data.DBHelper;
+import org.fdroid.fdroid.data.FDroidProviderTest;
+import org.fdroid.fdroid.data.Repo;
+import org.fdroid.fdroid.data.RepoProvider;
+import org.fdroid.fdroid.data.RepoProviderTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(RobolectricTestRunner.class)
+public class PanicResponderActivityTest extends FDroidProviderTest {
+
+ /**
+ * The {@link DBHelper} class populates the default repos when it first creates a database.
+ * The names/URLs/signing certificates for these repos are all hard coded in the source/res.
+ */
+ @Test
+ public void defaultRepos() {
+ List defaultRepos = RepoProvider.Helper.all(context);
+ assertEquals(defaultRepos.size(), 4); // based on app/src/main/res/default_repo.xml
+
+ Repo gpRepo = RepoProvider.Helper.findByAddress(context, "https://guardianproject.info/fdroid/repo");
+ setEnabled(gpRepo, true);
+ assertEquals(2, RepoProvider.Helper.countEnabledRepos(context));
+
+ PanicResponderActivity.resetRepos(context);
+ assertEquals(1, RepoProvider.Helper.countEnabledRepos(context));
+ defaultRepos = RepoProvider.Helper.all(context);
+ assertEquals(4, defaultRepos.size());
+
+ RepoProviderTest.insertRepo(
+ context,
+ "https://mock-repo-1.example.com/fdroid/repo",
+ "Just a made up repo",
+ "ABCDEF1234567890",
+ "Mock Repo 1"
+ );
+ defaultRepos = RepoProvider.Helper.all(context);
+ assertEquals(5, defaultRepos.size());
+ assertEquals(2, RepoProvider.Helper.countEnabledRepos(context));
+
+ PanicResponderActivity.resetRepos(context);
+ defaultRepos = RepoProvider.Helper.all(context);
+ assertEquals(4, defaultRepos.size());
+ assertEquals(1, RepoProvider.Helper.countEnabledRepos(context));
+ }
+}