Swap apps more robust on API < 11. Only show swap repos sometimes.

* Selecting apps to swap fixed

Before the checking of a list item would not actually register it to
be included in the swap. This has been rectified.

 * Added a new property to repos for "isSwap"

Repositories with this property are not shown in the Manage Repos
activity, as there is not much benefit to having this happen.

 * More robust error handling when symlinking files

Before it would check for stdout or stderr and then throw an exception.
This happened even on successful symlinks on my 2.3.3 device. As such,
I've put the error checking after the shell command has completely finished
(just in case there were any race conditions), and more importantly, checked
for the presence of the file being linked - rather than just stdout or
stderr.

 * More code cleanup

Generics <> operator, Nullable annotations, removal of dead code.
This commit is contained in:
Peter Serwylo 2015-01-02 14:27:03 +11:00
parent c6705e2cb9
commit 5036deb61e
15 changed files with 116 additions and 53 deletions

View File

@ -346,14 +346,15 @@ public class UpdateService extends IntentService implements ProgressListener {
List<Repo> repos = RepoProvider.Helper.all(this); List<Repo> repos = RepoProvider.Helper.all(this);
// Process each repo... // Process each repo...
Map<String, App> appsToUpdate = new HashMap<String, App>(); Map<String, App> appsToUpdate = new HashMap<>();
List<Apk> apksToUpdate = new ArrayList<Apk>(); List<Apk> apksToUpdate = new ArrayList<>();
List<Repo> unchangedRepos = new ArrayList<Repo>(); List<Repo> swapRepos = new ArrayList<>();
List<Repo> updatedRepos = new ArrayList<Repo>(); List<Repo> unchangedRepos = new ArrayList<>();
List<Repo> disabledRepos = new ArrayList<Repo>(); List<Repo> updatedRepos = new ArrayList<>();
List<CharSequence> errorRepos = new ArrayList<CharSequence>(); List<Repo> disabledRepos = new ArrayList<>();
ArrayList<CharSequence> repoErrors = new ArrayList<CharSequence>(); List<CharSequence> errorRepos = new ArrayList<>();
List<RepoUpdater.RepoUpdateRememberer> repoUpdateRememberers = new ArrayList<RepoUpdater.RepoUpdateRememberer>(); ArrayList<CharSequence> repoErrors = new ArrayList<>();
List<RepoUpdater.RepoUpdateRememberer> repoUpdateRememberers = new ArrayList<>();
boolean changes = false; boolean changes = false;
for (final Repo repo : repos) { for (final Repo repo : repos) {
@ -363,6 +364,9 @@ public class UpdateService extends IntentService implements ProgressListener {
} else if (!TextUtils.isEmpty(address) && !repo.address.equals(address)) { } else if (!TextUtils.isEmpty(address) && !repo.address.equals(address)) {
unchangedRepos.add(repo); unchangedRepos.add(repo);
continue; continue;
} else if (TextUtils.isEmpty(address) && repo.isSwap) {
swapRepos.add(repo);
continue;
} }
sendStatus(STATUS_INFO, getString(R.string.status_connecting_to_repo, repo.address)); sendStatus(STATUS_INFO, getString(R.string.status_connecting_to_repo, repo.address));

View File

@ -232,11 +232,7 @@ public final class Utils {
} }
eventType = xml.nextToken(); eventType = xml.nextToken();
} }
} catch (NameNotFoundException e) { } catch (NameNotFoundException | IOException | XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (XmlPullParserException e) {
e.printStackTrace(); e.printStackTrace();
} }
return 8; // some kind of hopeful default return 8; // some kind of hopeful default
@ -284,7 +280,7 @@ public final class Utils {
return displayFP; return displayFP;
} }
public static Uri getSharingUri(Context context, Repo repo) { public static Uri getSharingUri(Repo repo) {
if (TextUtils.isEmpty(repo.address)) if (TextUtils.isEmpty(repo.address))
return Uri.parse("http://wifi-not-enabled"); return Uri.parse("http://wifi-not-enabled");
Uri uri = Uri.parse(repo.address.replaceFirst("http", "fdroidrepo")); Uri uri = Uri.parse(repo.address.replaceFirst("http", "fdroidrepo"));
@ -347,8 +343,8 @@ public final class Utils {
digest.update(key); digest.update(key);
byte[] fingerprint = digest.digest(); byte[] fingerprint = digest.digest();
Formatter formatter = new Formatter(new StringBuilder()); Formatter formatter = new Formatter(new StringBuilder());
for (int i = 0; i < fingerprint.length; i++) { for (byte aFingerprint : fingerprint) {
formatter.format("%02X", fingerprint[i]); formatter.format("%02X", aFingerprint);
} }
ret = formatter.toString(); ret = formatter.toString();
formatter.close(); formatter.close();
@ -434,14 +430,14 @@ public final class Utils {
public static String getBinaryHash(File apk, String algo) { public static String getBinaryHash(File apk, String algo) {
FileInputStream fis = null; FileInputStream fis = null;
BufferedInputStream bis = null; BufferedInputStream bis;
try { try {
MessageDigest md = MessageDigest.getInstance(algo); MessageDigest md = MessageDigest.getInstance(algo);
fis = new FileInputStream(apk); fis = new FileInputStream(apk);
bis = new BufferedInputStream(fis); bis = new BufferedInputStream(fis);
byte[] dataBytes = new byte[524288]; byte[] dataBytes = new byte[524288];
int nread = 0; int nread;
while ((nread = bis.read(dataBytes)) != -1) while ((nread = bis.read(dataBytes)) != -1)
md.update(dataBytes, 0, nread); md.update(dataBytes, 0, nread);
@ -485,7 +481,7 @@ public final class Utils {
listNum = -1; listNum = -1;
else else
output.append('\n'); output.append('\n');
} else if (opening && tag.equals("ol")) { } else if (tag.equals("ol")) {
if (opening) if (opening)
listNum = 1; listNum = 1;
else else

View File

@ -33,7 +33,8 @@ public class DBHelper extends SQLiteOpenHelper {
+ "priority integer not null, pubkey text, fingerprint text, " + "priority integer not null, pubkey text, fingerprint text, "
+ "maxage integer not null default 0, " + "maxage integer not null default 0, "
+ "version integer not null default 0, " + "version integer not null default 0, "
+ "lastetag text, lastUpdated string);"; + "lastetag text, lastUpdated string,"
+ "isSwap integer boolean default 0);";
private static final String CREATE_TABLE_APK = private static final String CREATE_TABLE_APK =
"CREATE TABLE " + TABLE_APK + " ( " "CREATE TABLE " + TABLE_APK + " ( "
@ -98,7 +99,7 @@ public class DBHelper extends SQLiteOpenHelper {
+ InstalledAppProvider.DataColumns.APPLICATION_LABEL + " TEXT NOT NULL " + InstalledAppProvider.DataColumns.APPLICATION_LABEL + " TEXT NOT NULL "
+ " );"; + " );";
private static final int DB_VERSION = 46; private static final int DB_VERSION = 47;
private Context context; private Context context;
@ -273,6 +274,7 @@ public class DBHelper extends SQLiteOpenHelper {
populateRepoNames(db, oldVersion); populateRepoNames(db, oldVersion);
if (oldVersion < 43) createInstalledApp(db); if (oldVersion < 43) createInstalledApp(db);
addAppLabelToInstalledCache(db, oldVersion); addAppLabelToInstalledCache(db, oldVersion);
addIsSwapToRepo(db, oldVersion);
} }
/** /**
@ -400,6 +402,13 @@ public class DBHelper extends SQLiteOpenHelper {
} }
} }
private void addIsSwapToRepo(SQLiteDatabase db, int oldVersion) {
if (oldVersion < 47 && !columnExists(db, TABLE_REPO, "isSwap")) {
Log.i(TAG, "Adding isSwap field to " + TABLE_REPO + " table in db.");
db.execSQL("alter table " + TABLE_REPO + " add column isSwap boolean default 0;");
}
}
private void resetTransient(SQLiteDatabase db, int oldVersion) { private void resetTransient(SQLiteDatabase db, int oldVersion) {
// Before version 42, only transient info was stored in here. As of some time // Before version 42, only transient info was stored in here. As of some time
// just before 42 (F-Droid 0.60ish) it now has "ignore this version" info which // just before 42 (F-Droid 0.60ish) it now has "ignore this version" info which

View File

@ -30,6 +30,7 @@ public class Repo extends ValueObject {
public int maxage; // maximum age of index that will be accepted - 0 for any public int maxage; // maximum age of index that will be accepted - 0 for any
public String lastetag; // last etag we updated from, null forces update public String lastetag; // last etag we updated from, null forces update
public Date lastUpdated; public Date lastUpdated;
public boolean isSwap;
public Repo() { public Repo() {
@ -65,6 +66,8 @@ public class Repo extends ValueObject {
pubkey = cursor.getString(i); pubkey = cursor.getString(i);
} else if (column.equals(RepoProvider.DataColumns.PRIORITY)) { } else if (column.equals(RepoProvider.DataColumns.PRIORITY)) {
priority = cursor.getInt(i); priority = cursor.getInt(i);
} else if (column.equals(RepoProvider.DataColumns.IS_SWAP)) {
isSwap = cursor.getInt(i) == 1;
} }
} }
} }
@ -140,7 +143,7 @@ public class Repo extends ValueObject {
} }
if (values.containsKey(RepoProvider.DataColumns.IN_USE)) { if (values.containsKey(RepoProvider.DataColumns.IN_USE)) {
inuse = toInt(values.getAsInteger(RepoProvider.DataColumns.FINGERPRINT)) == 1; inuse = toInt(values.getAsInteger(RepoProvider.DataColumns.IN_USE)) == 1;
} }
if (values.containsKey(RepoProvider.DataColumns.LAST_UPDATED)) { if (values.containsKey(RepoProvider.DataColumns.LAST_UPDATED)) {
@ -173,5 +176,9 @@ public class Repo extends ValueObject {
if (values.containsKey(RepoProvider.DataColumns.PRIORITY)) { if (values.containsKey(RepoProvider.DataColumns.PRIORITY)) {
priority = toInt(values.getAsInteger(RepoProvider.DataColumns.PRIORITY)); priority = toInt(values.getAsInteger(RepoProvider.DataColumns.PRIORITY));
} }
if (values.containsKey(RepoProvider.DataColumns.IS_SWAP)) {
isSwap= toInt(values.getAsInteger(RepoProvider.DataColumns.IS_SWAP)) == 1;
}
} }
} }

View File

@ -218,19 +218,24 @@ public class RepoProvider extends FDroidProvider {
public static String LAST_ETAG = "lastetag"; public static String LAST_ETAG = "lastetag";
public static String LAST_UPDATED = "lastUpdated"; public static String LAST_UPDATED = "lastUpdated";
public static String VERSION = "version"; public static String VERSION = "version";
public static String IS_SWAP = "isSwap";
public static String[] ALL = { public static String[] ALL = {
_ID, ADDRESS, NAME, DESCRIPTION, IN_USE, PRIORITY, PUBLIC_KEY, _ID, ADDRESS, NAME, DESCRIPTION, IN_USE, PRIORITY, PUBLIC_KEY,
FINGERPRINT, MAX_AGE, LAST_UPDATED, LAST_ETAG, VERSION FINGERPRINT, MAX_AGE, LAST_UPDATED, LAST_ETAG, VERSION, IS_SWAP
}; };
} }
private static final String PROVIDER_NAME = "RepoProvider"; private static final String PROVIDER_NAME = "RepoProvider";
private static final String PATH_ALL_EXCEPT_SWAP = "allExceptSwap";
private static final int CODE_ALL_EXCEPT_SWAP = CODE_SINGLE + 1;
private static final UriMatcher matcher = new UriMatcher(-1); private static final UriMatcher matcher = new UriMatcher(-1);
static { static {
matcher.addURI(AUTHORITY + "." + PROVIDER_NAME, null, CODE_LIST); matcher.addURI(AUTHORITY + "." + PROVIDER_NAME, null, CODE_LIST);
matcher.addURI(AUTHORITY + "." + PROVIDER_NAME, PATH_ALL_EXCEPT_SWAP, CODE_ALL_EXCEPT_SWAP);
matcher.addURI(AUTHORITY + "." + PROVIDER_NAME, "#", CODE_SINGLE); matcher.addURI(AUTHORITY + "." + PROVIDER_NAME, "#", CODE_SINGLE);
} }
@ -242,6 +247,12 @@ public class RepoProvider extends FDroidProvider {
return ContentUris.withAppendedId(getContentUri(), repoId); return ContentUris.withAppendedId(getContentUri(), repoId);
} }
public static Uri allExceptSwapUri() {
return getContentUri().buildUpon()
.appendPath(PATH_ALL_EXCEPT_SWAP)
.build();
}
@Override @Override
protected String getTableName() { protected String getTableName() {
return DBHelper.TABLE_REPO; return DBHelper.TABLE_REPO;
@ -260,11 +271,12 @@ public class RepoProvider extends FDroidProvider {
@Override @Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
switch (matcher.match(uri)) {
case CODE_LIST:
if (TextUtils.isEmpty(sortOrder)) { if (TextUtils.isEmpty(sortOrder)) {
sortOrder = "_ID ASC"; sortOrder = "_ID ASC";
} }
switch (matcher.match(uri)) {
case CODE_LIST:
break; break;
case CODE_SINGLE: case CODE_SINGLE:
@ -272,6 +284,10 @@ public class RepoProvider extends FDroidProvider {
DataColumns._ID + " = " + uri.getLastPathSegment(); DataColumns._ID + " = " + uri.getLastPathSegment();
break; break;
case CODE_ALL_EXCEPT_SWAP:
selection = DataColumns.IS_SWAP + " = 0";
break;
default: default:
Log.e(TAG, "Invalid URI for repo content provider: " + uri); Log.e(TAG, "Invalid URI for repo content provider: " + uri);
throw new UnsupportedOperationException("Invalid URI for repo content provider: " + uri); throw new UnsupportedOperationException("Invalid URI for repo content provider: " + uri);

View File

@ -1,6 +1,5 @@
package org.fdroid.fdroid.localrepo; package org.fdroid.fdroid.localrepo;
import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
@ -255,7 +254,6 @@ public class LocalRepoManager {
} }
} }
@TargetApi(9)
public void addApp(Context context, String packageName) { public void addApp(Context context, String packageName) {
App app; App app;
try { try {

View File

@ -82,7 +82,7 @@ public class WifiStateChangeService extends Service {
Context context = WifiStateChangeService.this.getApplicationContext(); Context context = WifiStateChangeService.this.getApplicationContext();
LocalRepoManager lrm = LocalRepoManager.get(context); LocalRepoManager lrm = LocalRepoManager.get(context);
lrm.writeIndexPage(Utils.getSharingUri(context, FDroidApp.repo).toString()); lrm.writeIndexPage(Utils.getSharingUri(FDroidApp.repo).toString());
if (isCancelled()) if (isCancelled())
return null; return null;

View File

@ -246,7 +246,7 @@ public class LocalRepoActivity extends ActionBarActivity {
* wifi AP to join. Lots of QR Scanners are buggy and do not respect * wifi AP to join. Lots of QR Scanners are buggy and do not respect
* custom URI schemes, so we have to use http:// or https:// :-( * custom URI schemes, so we have to use http:// or https:// :-(
*/ */
final String qrUriString = Utils.getSharingUri(this, FDroidApp.repo).toString() final String qrUriString = Utils.getSharingUri(FDroidApp.repo).toString()
.replaceFirst("fdroidrepo", "http") .replaceFirst("fdroidrepo", "http")
.replaceAll("ssid=[^?]*", "") .replaceAll("ssid=[^?]*", "")
.toUpperCase(Locale.ENGLISH); .toUpperCase(Locale.ENGLISH);
@ -270,7 +270,7 @@ public class LocalRepoActivity extends ActionBarActivity {
if (nfcAdapter == null) if (nfcAdapter == null)
return; return;
nfcAdapter.setNdefPushMessage(new NdefMessage(new NdefRecord[] { nfcAdapter.setNdefPushMessage(new NdefMessage(new NdefRecord[] {
NdefRecord.createUri(Utils.getSharingUri(this, FDroidApp.repo)), NdefRecord.createUri(Utils.getSharingUri(FDroidApp.repo)),
}), this); }), this);
} }
} }
@ -292,7 +292,7 @@ public class LocalRepoActivity extends ActionBarActivity {
progressDialog = new ProgressDialog(c); progressDialog = new ProgressDialog(c);
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.setTitle(R.string.updating); progressDialog.setTitle(R.string.updating);
sharingUri = Utils.getSharingUri(c, FDroidApp.repo); sharingUri = Utils.getSharingUri(FDroidApp.repo);
} }
@Override @Override

View File

@ -611,7 +611,7 @@ public class ManageReposActivity extends ActionBarActivity {
@Override @Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
Uri uri = RepoProvider.getContentUri(); Uri uri = RepoProvider.allExceptSwapUri();
Log.i(TAG, "Creating repo loader '" + uri + "'."); Log.i(TAG, "Creating repo loader '" + uri + "'.");
String[] projection = { String[] projection = {
RepoProvider.DataColumns._ID, RepoProvider.DataColumns._ID,

View File

@ -66,7 +66,7 @@ public class RepoDetailsActivity extends ActionBarActivity {
@TargetApi(14) @TargetApi(14)
private void setNfc() { private void setNfc() {
if (NfcHelper.setPushMessage(this, Utils.getSharingUri(this, repo))) { if (NfcHelper.setPushMessage(this, Utils.getSharingUri(repo))) {
findViewById(android.R.id.content).post(new Runnable() { findViewById(android.R.id.content).post(new Runnable() {
@Override @Override
public void run() { public void run() {

View File

@ -4,7 +4,9 @@ import android.app.Activity;
import android.content.ContentValues; import android.content.ContentValues;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -18,6 +20,8 @@ import org.fdroid.fdroid.data.RepoProvider;
public class ConfirmReceiveSwapFragment extends Fragment implements ProgressListener { public class ConfirmReceiveSwapFragment extends Fragment implements ProgressListener {
private static final String TAG = "org.fdroid.fdroid.views.swap.ConfirmReceiveSwapFragment";
private NewRepoConfig newRepoConfig; private NewRepoConfig newRepoConfig;
@Override @Override
@ -64,22 +68,29 @@ public class ConfirmReceiveSwapFragment extends Fragment implements ProgressList
UpdateService.updateRepoNow(repo.address, getActivity()).setListener(this); UpdateService.updateRepoNow(repo.address, getActivity()).setListener(this);
} }
@NonNull
private Repo ensureRepoExists() { private Repo ensureRepoExists() {
// TODO: newRepoConfig.getUri() will include a fingerprint, which may not match with // TODO: newRepoConfig.getUri() will include a fingerprint, which may not match with
// the repos address in the database. // the repos address in the database.
Repo repo = RepoProvider.Helper.findByAddress(getActivity(), newRepoConfig.getUriString()); Repo repo = RepoProvider.Helper.findByAddress(getActivity(), newRepoConfig.getUriString());
if (repo == null) { if (repo == null) {
ContentValues values = new ContentValues(5); ContentValues values = new ContentValues(6);
// TODO: i18n and think about most appropriate name. Although ideally, it will not be seen often, // TODO: i18n and think about most appropriate name. Although it wont be visible in
// because we're whacking a pretty UI over the swap process so they don't need to "Manage repos"... // the "Manage repos" UI after being marked as a swap repo here...
values.put(RepoProvider.DataColumns.NAME, "Swap"); values.put(RepoProvider.DataColumns.NAME, "Swap");
values.put(RepoProvider.DataColumns.ADDRESS, newRepoConfig.getUriString()); values.put(RepoProvider.DataColumns.ADDRESS, newRepoConfig.getUriString());
values.put(RepoProvider.DataColumns.DESCRIPTION, ""); // TODO; values.put(RepoProvider.DataColumns.DESCRIPTION, ""); // TODO;
values.put(RepoProvider.DataColumns.FINGERPRINT, newRepoConfig.getFingerprint()); values.put(RepoProvider.DataColumns.FINGERPRINT, newRepoConfig.getFingerprint());
values.put(RepoProvider.DataColumns.IN_USE, true); values.put(RepoProvider.DataColumns.IN_USE, true);
values.put(RepoProvider.DataColumns.IS_SWAP, true);
Uri uri = RepoProvider.Helper.insert(getActivity(), values); Uri uri = RepoProvider.Helper.insert(getActivity(), values);
repo = RepoProvider.Helper.findByUri(getActivity(), uri); repo = RepoProvider.Helper.findByUri(getActivity(), uri);
} else if (!repo.isSwap) {
Log.d(TAG, "Old local repo being marked as \"Swap\" repo, so that it wont appear in the list of repositories in the future.");
ContentValues values = new ContentValues(1);
values.put(RepoProvider.DataColumns.IS_SWAP, true);
RepoProvider.Helper.update(getActivity(), repo, values);
} }
return repo; return repo;
} }

View File

@ -13,7 +13,6 @@ import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat; import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.CursorAdapter; import android.support.v4.widget.CursorAdapter;
import android.support.v4.widget.SimpleCursorAdapter;
import android.support.v7.widget.SearchView; import android.support.v7.widget.SearchView;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.*; import android.view.*;
@ -30,8 +29,13 @@ import java.util.Set;
public class SelectAppsFragment extends ThemeableListFragment public class SelectAppsFragment extends ThemeableListFragment
implements LoaderManager.LoaderCallbacks<Cursor>, SearchView.OnQueryTextListener { implements LoaderManager.LoaderCallbacks<Cursor>, SearchView.OnQueryTextListener {
@SuppressWarnings("UnusedDeclaration")
private static final String TAG = "org.fdroid.fdroid.views.swap.SelectAppsFragment";
private String mCurrentFilterString; private String mCurrentFilterString;
private Set<String> previouslySelectedApps = new HashSet<>();
@NonNull
private final Set<String> previouslySelectedApps = new HashSet<>();
public Set<String> getSelectedApps() { public Set<String> getSelectedApps() {
return FDroidApp.selectedApps; return FDroidApp.selectedApps;
@ -120,7 +124,11 @@ public class SelectAppsFragment extends ThemeableListFragment
@Override @Override
public void onListItemClick(ListView l, View v, int position, long id) { public void onListItemClick(ListView l, View v, int position, long id) {
Cursor c = (Cursor) l.getAdapter().getItem(position); toggleAppSelected(position);
}
private void toggleAppSelected(int position) {
Cursor c = (Cursor) getListAdapter().getItem(position);
String packageName = c.getString(c.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID)); String packageName = c.getString(c.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID));
if (FDroidApp.selectedApps.contains(packageName)) { if (FDroidApp.selectedApps.contains(packageName)) {
FDroidApp.selectedApps.remove(packageName); FDroidApp.selectedApps.remove(packageName);
@ -208,7 +216,10 @@ public class SelectAppsFragment extends ThemeableListFragment
return R.layout.swap_create_header; return R.layout.swap_create_header;
} }
private static class AppListAdapter extends CursorAdapter { private class AppListAdapter extends CursorAdapter {
@SuppressWarnings("UnusedDeclaration")
private static final String TAG = "org.fdroid.fdroid.views.swap.SelectAppsFragment.AppListAdapter";
@Nullable @Nullable
private LayoutInflater inflater; private LayoutInflater inflater;
@ -276,12 +287,16 @@ public class SelectAppsFragment extends ThemeableListFragment
if (checkBoxView != null) { if (checkBoxView != null) {
CheckBox checkBox = (CheckBox)checkBoxView; CheckBox checkBox = (CheckBox)checkBoxView;
checkBox.setOnCheckedChangeListener(null); checkBox.setOnCheckedChangeListener(null);
checkBox.setChecked(listView.isItemChecked(cursor.getPosition()));
final int position = cursor.getPosition(); final int cursorPosition = cursor.getPosition();
final int listPosition = cursor.getPosition() + 1; // To account for the header view.
checkBox.setChecked(listView.isItemChecked(listPosition));
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override @Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
listView.setItemChecked(position, isChecked); listView.setItemChecked(listPosition, isChecked);
toggleAppSelected(cursorPosition);
} }
}); });
} }

View File

@ -5,6 +5,7 @@ import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
@ -114,7 +115,7 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
// Even if they opted to skip the message which says "Touch devices to swap", // Even if they opted to skip the message which says "Touch devices to swap",
// we still want to actually enable the feature, so that they could touch // we still want to actually enable the feature, so that they could touch
// during the wifi qr code being shown too. // during the wifi qr code being shown too.
boolean nfcMessageReady = NfcHelper.setPushMessage(this, Utils.getSharingUri(this, FDroidApp.repo)); boolean nfcMessageReady = NfcHelper.setPushMessage(this, Utils.getSharingUri(FDroidApp.repo));
if (Preferences.get().showNfcDuringSwap() && nfcMessageReady) { if (Preferences.get().showNfcDuringSwap() && nfcMessageReady) {
showFragment(new NfcSwapFragment(), STATE_NFC); showFragment(new NfcSwapFragment(), STATE_NFC);
@ -200,16 +201,16 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
class UpdateAsyncTask extends AsyncTask<Void, String, Void> { class UpdateAsyncTask extends AsyncTask<Void, String, Void> {
private static final String TAG = "fdroid.SwapActivity.UpdateAsyncTask"; private static final String TAG = "fdroid.SwapActivity.UpdateAsyncTask";
private ProgressDialog progressDialog; private final ProgressDialog progressDialog;
private Set<String> selectedApps; private final Set<String> selectedApps;
private Uri sharingUri; private final Uri sharingUri;
public UpdateAsyncTask(Context c, Set<String> apps) { public UpdateAsyncTask(Context c, @NonNull Set<String> apps) {
selectedApps = apps; selectedApps = apps;
progressDialog = new ProgressDialog(c); progressDialog = new ProgressDialog(c);
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.setTitle(R.string.updating); progressDialog.setTitle(R.string.updating);
sharingUri = Utils.getSharingUri(c, FDroidApp.repo); sharingUri = Utils.getSharingUri(FDroidApp.repo);
} }
@Override @Override

View File

@ -1,5 +1,11 @@
package org.fdroid.fdroid.views.swap; package org.fdroid.fdroid.views.swap;
/**
* Defines the contract between the {@link org.fdroid.fdroid.views.swap.SwapActivity}
* and the fragments which live in it. The fragments each have the responsibility of
* moving to the next stage of the process, and are entitled to stop swapping too
* (e.g. when a "Cancel" button is pressed).
*/
public interface SwapProcessManager { public interface SwapProcessManager {
public void nextStep(); public void nextStep();
public void stopSwapping(); public void stopSwapping();

View File

@ -112,7 +112,7 @@ public class WifiQrFragment extends Fragment {
private void setUIFromWifi() { private void setUIFromWifi() {
if (TextUtils.isEmpty(FDroidApp.repo.address)) if (TextUtils.isEmpty(FDroidApp.repo.address) || getView() == null)
return; return;
String scheme = Preferences.get().isLocalRepoHttpsEnabled() ? "https://" : "http://"; String scheme = Preferences.get().isLocalRepoHttpsEnabled() ? "https://" : "http://";
@ -129,7 +129,7 @@ public class WifiQrFragment extends Fragment {
* wifi AP to join. Lots of QR Scanners are buggy and do not respect * wifi AP to join. Lots of QR Scanners are buggy and do not respect
* custom URI schemes, so we have to use http:// or https:// :-( * custom URI schemes, so we have to use http:// or https:// :-(
*/ */
Uri sharingUri = Utils.getSharingUri(getActivity(), FDroidApp.repo); Uri sharingUri = Utils.getSharingUri(FDroidApp.repo);
String qrUriString = ( scheme + sharingUri.getHost() ).toUpperCase(Locale.ENGLISH); String qrUriString = ( scheme + sharingUri.getHost() ).toUpperCase(Locale.ENGLISH);
if (sharingUri.getPort() != 80) { if (sharingUri.getPort() != 80) {
qrUriString += ":" + sharingUri.getPort(); qrUriString += ":" + sharingUri.getPort();