Merge branch 'panic-app-wipe' into 'master'
panic app and repo wipe See merge request fdroid/fdroidclient!827
This commit is contained in:
commit
ca81518f74
@ -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'
|
||||
|
@ -104,6 +104,10 @@
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".views.panic.SelectInstalledAppsActivity"
|
||||
android:parentActivityName=".views.panic.PanicPreferencesActivity"/>
|
||||
|
||||
<activity
|
||||
android:name=".views.panic.PanicResponderActivity"
|
||||
android:noHistory="true"
|
||||
|
@ -0,0 +1,41 @@
|
||||
package org.fdroid.fdroid.views.panic;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.support.v7.preference.CheckBoxPreference;
|
||||
import android.support.v7.preference.PreferenceViewHolder;
|
||||
import android.util.AttributeSet;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.R;
|
||||
|
||||
public class DestructiveCheckBoxPreference extends CheckBoxPreference {
|
||||
public DestructiveCheckBoxPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public DestructiveCheckBoxPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public DestructiveCheckBoxPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public DestructiveCheckBoxPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
if (!holder.itemView.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
Resources resources = getContext().getResources();
|
||||
if (FDroidApp.isAppThemeLight()) {
|
||||
holder.itemView.setBackgroundColor(resources.getColor(R.color.panic_destructive_light));
|
||||
} else {
|
||||
holder.itemView.setBackgroundColor(resources.getColor(R.color.panic_destructive_dark));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package org.fdroid.fdroid.views.panic;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceViewHolder;
|
||||
import android.util.AttributeSet;
|
||||
import org.fdroid.fdroid.FDroidApp;
|
||||
import org.fdroid.fdroid.R;
|
||||
|
||||
public class DestructivePreference extends Preference {
|
||||
public DestructivePreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public DestructivePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public DestructivePreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public DestructivePreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
if (FDroidApp.isAppThemeLight()) {
|
||||
holder.itemView.setBackgroundColor(getContext().getResources().getColor(R.color.panic_destructive_light));
|
||||
} else {
|
||||
holder.itemView.setBackgroundColor(getContext().getResources().getColor(R.color.panic_destructive_dark));
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.LightingColorFilter;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
@ -20,37 +21,42 @@ import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.preference.CheckBoxPreference;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceCategory;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
import info.guardianproject.panic.Panic;
|
||||
import info.guardianproject.panic.PanicResponder;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.installer.PrivilegedInstaller;
|
||||
import org.fdroid.fdroid.views.hiding.HidingManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
|
||||
public class PanicPreferencesFragment extends PreferenceFragment
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private static final String PREF_EXIT = Preferences.PREF_PANIC_EXIT;
|
||||
private static final String PREF_APP = "pref_panic_app";
|
||||
private static final String PREF_HIDE = Preferences.PREF_PANIC_HIDE;
|
||||
|
||||
private PackageManager pm;
|
||||
private ListPreference prefApp;
|
||||
private CheckBoxPreference prefExit;
|
||||
private CheckBoxPreference prefHide;
|
||||
private CheckBoxPreference prefResetRepos;
|
||||
private PreferenceCategory categoryAppsToUninstall;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle bundle, String s) {
|
||||
addPreferencesFromResource(R.xml.preferences_panic);
|
||||
|
||||
pm = getActivity().getPackageManager();
|
||||
prefExit = (CheckBoxPreference) findPreference(PREF_EXIT);
|
||||
prefExit = (CheckBoxPreference) findPreference(Preferences.PREF_PANIC_EXIT);
|
||||
prefApp = (ListPreference) findPreference(PREF_APP);
|
||||
prefHide = (CheckBoxPreference) findPreference(PREF_HIDE);
|
||||
prefHide = (CheckBoxPreference) findPreference(Preferences.PREF_PANIC_HIDE);
|
||||
prefHide.setTitle(getString(R.string.panic_hide_title, getString(R.string.app_name)));
|
||||
prefResetRepos = (CheckBoxPreference) findPreference(Preferences.PREF_PANIC_RESET_REPOS);
|
||||
categoryAppsToUninstall = (PreferenceCategory) findPreference("pref_panic_apps_to_uninstall");
|
||||
|
||||
if (PanicResponder.checkForDisconnectIntent(getActivity())) {
|
||||
// the necessary action should have been performed by the check already
|
||||
@ -73,9 +79,12 @@ public class PanicPreferencesFragment extends PreferenceFragment
|
||||
if (packageName.equals(Panic.PACKAGE_NAME_NONE)) {
|
||||
prefHide.setChecked(false);
|
||||
prefHide.setEnabled(false);
|
||||
prefResetRepos.setChecked(false);
|
||||
prefResetRepos.setEnabled(false);
|
||||
getActivity().setResult(Activity.RESULT_CANCELED);
|
||||
} else {
|
||||
prefHide.setEnabled(true);
|
||||
prefResetRepos.setEnabled(true);
|
||||
}
|
||||
showPanicApp(packageName);
|
||||
return true;
|
||||
@ -88,6 +97,48 @@ public class PanicPreferencesFragment extends PreferenceFragment
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
|
||||
|
||||
if (!PrivilegedInstaller.isDefault(getActivity())) {
|
||||
getPreferenceScreen().removePreference(categoryAppsToUninstall);
|
||||
return;
|
||||
}
|
||||
showWipeList();
|
||||
}
|
||||
|
||||
private void showWipeList() {
|
||||
Intent intent = new Intent(getActivity(), SelectInstalledAppsActivity.class);
|
||||
intent.setAction(Intent.ACTION_MAIN);
|
||||
Set<String> 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);
|
||||
|
@ -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<String> wipeList = new ArrayList<>(preferences.getPanicWipeSet());
|
||||
preferences.setPanicWipeSet(Collections.<String>emptySet());
|
||||
preferences.setPanicTmpSelectedSet(Collections.<String>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()) {
|
||||
if (!runningAppUninstalls && preferences.panicExit()) {
|
||||
exitAndClear();
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
static void resetRepos(Context context) {
|
||||
HashSet<String> enabledAddresses = new HashSet<>();
|
||||
HashSet<String> 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<Repo> 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();
|
||||
}
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<String> 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);
|
||||
}
|
||||
}
|
@ -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<String> selectedApps;
|
||||
|
||||
public SelectInstalledAppListItemController(Activity activity, View itemView, Set<String> 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);
|
||||
}
|
||||
}
|
@ -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<Cursor> {
|
||||
|
||||
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<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
return new CursorLoader(this, InstalledAppProvider.getAllAppsUri(), null, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<Cursor> 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<Cursor> 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);
|
||||
}
|
||||
}
|
@ -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<Cursor> {
|
||||
@ -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
|
||||
|
8
app/src/full/res/drawable/check.xml
Normal file
8
app/src/full/res/drawable/check.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<!-- drawable/check.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z" />
|
||||
</vector>
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<resources>
|
||||
|
||||
<color name="swap_incompatible">#ff7900</color>
|
||||
<color name="swap_light_blue">#27aae1</color>
|
||||
@ -10,4 +10,6 @@
|
||||
<color name="swap_grey_icon">#4a4a4a</color>
|
||||
<color name="swap_light_grey_icon">#bbbbbb</color>
|
||||
|
||||
<color name="panic_destructive_light">#fff0f0</color>
|
||||
<color name="panic_destructive_dark">#330000</color>
|
||||
</resources>
|
||||
|
@ -19,13 +19,26 @@
|
||||
android:title="@string/panic_app_setting_title"
|
||||
tools:icon="@drawable/ic_cancel"/>
|
||||
|
||||
<android.support.v7.preference.CheckBoxPreference
|
||||
<org.fdroid.fdroid.views.panic.DestructiveCheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:enabled="false"
|
||||
android:key="pref_panic_hide"
|
||||
android:summary="@string/panic_hide_summary"
|
||||
android:title="@string/panic_hide_title"/>
|
||||
|
||||
<org.fdroid.fdroid.views.panic.DestructiveCheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:enabled="false"
|
||||
android:key="pref_panic_reset_repos"
|
||||
android:summary="@string/panic_reset_repos_summary"
|
||||
android:title="@string/panic_reset_repos_title"/>
|
||||
|
||||
</android.support.v7.preference.PreferenceCategory>
|
||||
|
||||
<android.support.v7.preference.PreferenceCategory
|
||||
android:key="pref_panic_apps_to_uninstall"
|
||||
android:title="@string/panic_apps_to_uninstall">
|
||||
|
||||
</android.support.v7.preference.PreferenceCategory>
|
||||
|
||||
</android.support.v7.preference.PreferenceScreen>
|
@ -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<String> getPanicTmpSelectedSet() {
|
||||
return preferences.getStringSet(Preferences.PREF_PANIC_TMP_SELECTED_SET, Collections.<String>emptySet());
|
||||
}
|
||||
|
||||
public void setPanicTmpSelectedSet(Set<String> selectedSet) {
|
||||
preferences.edit().putStringSet(Preferences.PREF_PANIC_TMP_SELECTED_SET, selectedSet).apply();
|
||||
}
|
||||
|
||||
public Set<String> getPanicWipeSet() {
|
||||
return preferences.getStringSet(Preferences.PREF_PANIC_WIPE_SET, Collections.<String>emptySet());
|
||||
}
|
||||
|
||||
public void setPanicWipeSet(Set<String> 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".
|
||||
|
@ -86,25 +86,38 @@ public class ApkProvider extends FDroidProvider {
|
||||
* <li>If installed, limit to apks signed by the same signer as the installed apk.</li>
|
||||
* <li>Otherwise, limit to apks signed by the "preferred" signer (see {@link App#preferredSigner}).</li>
|
||||
* </ul>
|
||||
* 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<Apk> 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) {
|
||||
|
@ -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<App> appList = new ArrayList<>();
|
||||
Cursor cursor = context.getContentResolver().query(InstalledAppProvider.getAllAppsUri(),
|
||||
null, null, null, null);
|
||||
if (cursor != null) {
|
||||
if (cursor.getCount() > 0) {
|
||||
cursor.moveToFirst();
|
||||
while (!cursor.isAfterLast()) {
|
||||
appList.add(new App(cursor));
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
return appList.toArray(new App[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The keys are the package names, and their corresponding values are
|
||||
* the {@link PackageInfo#lastUpdateTime last update time} in milliseconds.
|
||||
*/
|
||||
public static Map<String, Long> all(Context context) {
|
||||
public static Map<String, Long> lastUpdateTimes(Context context) {
|
||||
|
||||
Map<String, Long> 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();
|
||||
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);
|
||||
if (projection.length == 0) {
|
||||
query.addField(Cols._ID);
|
||||
}
|
||||
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) {
|
||||
|
||||
|
@ -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<String, Long> cachedInfo = InstalledAppProvider.Helper.all(context);
|
||||
Map<String, Long> cachedInfo = InstalledAppProvider.Helper.lastUpdateTimes(context);
|
||||
|
||||
List<PackageInfo> packageInfoList = context.getPackageManager()
|
||||
.getInstalledPackages(PackageManager.GET_SIGNATURES);
|
||||
|
@ -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;
|
||||
|
||||
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().
|
||||
*
|
||||
* @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<String> wipeSet = prefs.getPanicTmpSelectedSet();
|
||||
checkBox.toggle();
|
||||
if (checkBox.isChecked()) {
|
||||
wipeSet.add(currentApp.packageName);
|
||||
} else {
|
||||
wipeSet.remove(currentApp.packageName);
|
||||
}
|
||||
prefs.setPanicTmpSelectedSet(wipeSet);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<InstalledAppListItemController> {
|
||||
public class InstalledAppListAdapter extends RecyclerView.Adapter<InstalledAppListItemController> {
|
||||
|
||||
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<InstalledAppListItemC
|
||||
}
|
||||
|
||||
cursor.moveToPosition(position);
|
||||
return cursor.getLong(cursor.getColumnIndex(Schema.AppMetadataTable.Cols.ROW_ID));
|
||||
// TODO this should be based on Schema.InstalledAppProvider.Cols._ID
|
||||
return cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
@ -71,4 +71,14 @@
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/icon"
|
||||
android:clickable="false"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
@ -206,6 +206,7 @@ This often occurs with apps installed via Google Play or other sources, if they
|
||||
<string name="menu_install">Install</string>
|
||||
<!-- This is a button label, it must be the right size, or the layout gets messed up. It should ideally be 10-20 characters. -->
|
||||
<string name="menu_uninstall">Uninstall</string>
|
||||
<string name="menu_select_for_wipe">Select for wipe</string>
|
||||
<string name="menu_ignore_all">Ignore All Updates</string>
|
||||
<string name="menu_ignore_this">Ignore This Update</string>
|
||||
<string name="menu_website">Website</string>
|
||||
@ -323,13 +324,18 @@ This often occurs with apps installed via Google Play or other sources, if they
|
||||
<!-- "panic button" is a well known concept in English, it is good to find a similar metaphor in your language -->
|
||||
<string name="panic_settings">Panic button settings</string>
|
||||
<string name="panic_settings_summary">Actions to be taken in case of emergency</string>
|
||||
<string name="panic_exit_title">Exit App</string>
|
||||
<string name="panic_exit_summary">App will be closed </string>
|
||||
<string name="panic_exit_title">Exit app</string>
|
||||
<string name="panic_exit_summary">This app will be closed</string>
|
||||
<string name="panic_destructive_actions">Destructive Actions</string>
|
||||
<string name="panic_will_be_wiped">Will be uninstalled and all data deleted</string>
|
||||
<string name="panic_apps_to_uninstall">Apps to be uninstalled and all data wiped</string>
|
||||
<string name="panic_add_apps_to_uninstall">Add apps to be uninstalled and wiped</string>
|
||||
<string name="panic_hide_title">Hide %s</string>
|
||||
<string name="panic_hide_summary">App will hide itself</string>
|
||||
<string name="panic_hide_warning_title">Remember how to restore</string>
|
||||
<string name="panic_hide_warning_message">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.</string>
|
||||
<string name="panic_reset_repos_title">Reset repos</string>
|
||||
<string name="panic_reset_repos_summary">Force the repo setup back to defaults</string>
|
||||
|
||||
<!-- The name of a fake calculator app used as a disguise -->
|
||||
<string name="hiding_calculator">Calculator</string>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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<String, Long> foundBefore = InstalledAppProvider.Helper.all(RuntimeEnvironment.application);
|
||||
Map<String, Long> 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<String, Long> foundAfter = InstalledAppProvider.Helper.all(RuntimeEnvironment.application);
|
||||
Map<String, Long> 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
|
||||
|
@ -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));
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
package org.fdroid.fdroid.updater;
|
||||
|
||||
import android.content.ContentValues;
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
package org.fdroid.fdroid.updater;
|
||||
|
||||
import org.fdroid.fdroid.BuildConfig;
|
||||
|
@ -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();
|
||||
@ -126,7 +119,7 @@ public class Issue763MultiRepo extends MultiIndexUpdaterTest {
|
||||
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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
|
@ -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<Repo> 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));
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user