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 Preference(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
@@ -156,6 +204,7 @@ public class PanicPreferencesFragment extends PreferenceFragment
// disable destructive panic actions
prefHide.setEnabled(false);
+ showWipeList();
} else {
// try to display connected panic app
try {
@@ -163,6 +212,7 @@ public class PanicPreferencesFragment extends PreferenceFragment
prefApp.setSummary(pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)));
prefApp.setIcon(pm.getApplicationIcon(packageName));
prefHide.setEnabled(true);
+ showWipeList();
} catch (PackageManager.NameNotFoundException e) {
// revert back to no app, just to be safe
PanicResponder.setTriggerPackageName(getActivity(), Panic.PACKAGE_NAME_NONE);
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..3d222cff9
--- /dev/null
+++ b/app/src/full/java/org/fdroid/fdroid/views/panic/SelectInstalledAppsActivity.java
@@ -0,0 +1,144 @@
+/*
+ * 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.AppProvider;
+import org.fdroid.fdroid.data.Schema;
+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,
+ AppProvider.getInstalledUri(),
+ Schema.AppMetadataTable.Cols.ALL,
+ 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/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/xml/preferences_panic.xml b/app/src/full/res/xml/preferences_panic.xml
index e26d3eabb..79f9925fc 100644
--- a/app/src/full/res/xml/preferences_panic.xml
+++ b/app/src/full/res/xml/preferences_panic.xml
@@ -28,4 +28,10 @@
+
+
+
+
\ 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..e129bb7db 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,8 @@ 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_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";
@@ -515,6 +519,22 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
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/views/apps/AppListItemController.java b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java
index ad434b619..e8825bbbf 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
@@ -22,15 +22,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 +45,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 +65,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 +100,9 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
@Nullable
private final Button secondaryButton;
+ @Nullable
+ private final CheckBox checkBox;
+
@Nullable
private App currentApp;
@@ -107,6 +113,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 +154,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);
@@ -220,9 +230,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 +338,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 +555,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..b5ca5d692 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
@@ -11,14 +11,14 @@ 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);
}
diff --git a/app/src/main/res/layout/installed_app_list_item.xml b/app/src/main/res/layout/installed_app_list_item.xml
index e65779c86..87b44a3a4 100644
--- a/app/src/main/res/layout/installed_app_list_item.xml
+++ b/app/src/main/res/layout/installed_app_list_item.xml
@@ -71,4 +71,14 @@
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp" />
+
+
\ 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..e67a1b3b6 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,9 +324,12 @@ 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