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)); + } +}