From 85410504dacd2fafd0fd51bff35d74dca2f39ada Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Tue, 7 Aug 2018 13:37:04 +0200 Subject: [PATCH 01/41] LocalRepoService for setting up the local swap repo This moves all logic for setting up the local fdroid repo to its own IntentService. That makes it much easier to interact with since things can just use the static helper method to request it to update, and it'll do the right thing. --- .../fdroid/updater/SwapRepoEmulatorTest.java | 199 ++++++++++++++++++ app/src/full/AndroidManifest.xml | 3 + .../fdroid/localrepo/LocalRepoManager.java | 6 + .../fdroid/localrepo/LocalRepoService.java | 163 ++++++++++++++ .../views/swap/SwapWorkflowActivity.java | 116 ++-------- .../org/fdroid/fdroid/data/ShadowApp.java | 14 ++ .../fdroid/fdroid/updater/SwapRepoTest.java | 187 ++++++++++++++++ 7 files changed, 593 insertions(+), 95 deletions(-) create mode 100644 app/src/androidTest/java/org/fdroid/fdroid/updater/SwapRepoEmulatorTest.java create mode 100644 app/src/full/java/org/fdroid/fdroid/localrepo/LocalRepoService.java create mode 100644 app/src/testFull/java/org/fdroid/fdroid/data/ShadowApp.java create mode 100644 app/src/testFull/java/org/fdroid/fdroid/updater/SwapRepoTest.java diff --git a/app/src/androidTest/java/org/fdroid/fdroid/updater/SwapRepoEmulatorTest.java b/app/src/androidTest/java/org/fdroid/fdroid/updater/SwapRepoEmulatorTest.java new file mode 100644 index 000000000..390763942 --- /dev/null +++ b/app/src/androidTest/java/org/fdroid/fdroid/updater/SwapRepoEmulatorTest.java @@ -0,0 +1,199 @@ +package org.fdroid.fdroid.updater; + +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.ResolveInfo; +import android.os.Looper; +import android.support.test.InstrumentationRegistry; +import android.text.TextUtils; +import android.util.Log; +import org.fdroid.fdroid.BuildConfig; +import org.fdroid.fdroid.FDroidApp; +import org.fdroid.fdroid.Hasher; +import org.fdroid.fdroid.IndexUpdater; +import org.fdroid.fdroid.Preferences; +import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.data.Apk; +import org.fdroid.fdroid.data.ApkProvider; +import org.fdroid.fdroid.data.App; +import org.fdroid.fdroid.data.AppProvider; +import org.fdroid.fdroid.data.Repo; +import org.fdroid.fdroid.data.RepoProvider; +import org.fdroid.fdroid.data.Schema; +import org.fdroid.fdroid.localrepo.LocalRepoKeyStore; +import org.fdroid.fdroid.localrepo.LocalRepoManager; +import org.fdroid.fdroid.localrepo.LocalRepoService; +import org.fdroid.fdroid.net.LocalHTTPD; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.net.Socket; +import java.security.cert.Certificate; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class SwapRepoEmulatorTest { + public static final String TAG = "SwapRepoEmulatorTest"; + + /** + * @see org.fdroid.fdroid.net.WifiStateChangeService.WifiInfoThread#run() + */ + @Test + public void testSwap() + throws IOException, LocalRepoKeyStore.InitException, IndexUpdater.UpdateException, InterruptedException { + Looper.prepare(); + LocalHTTPD localHttpd = null; + try { + Log.i(TAG, "REPO: " + FDroidApp.repo); + final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + Preferences.setupForTests(context); + + FDroidApp.initWifiSettings(); + assertNull(FDroidApp.repo.address); + + final CountDownLatch latch = new CountDownLatch(1); + new Thread() { + @Override + public void run() { + while (FDroidApp.repo.address == null) { + try { + Log.i(TAG, "Waiting for IP address... " + FDroidApp.repo.address); + Thread.sleep(1000); + } catch (InterruptedException e) { + // ignored + } + } + latch.countDown(); + } + }.start(); + latch.await(10, TimeUnit.MINUTES); + assertNotNull(FDroidApp.repo.address); + + LocalRepoService.runProcess(context, new String[]{context.getPackageName()}); + Log.i(TAG, "REPO: " + FDroidApp.repo); + File indexJarFile = LocalRepoManager.get(context).getIndexJar(); + assertTrue(indexJarFile.isFile()); + + localHttpd = new LocalHTTPD( + context, + null, + FDroidApp.port, + LocalRepoManager.get(context).getWebRoot(), + false); + localHttpd.start(); + Thread.sleep(100); // give the server some tine to start. + assertTrue(localHttpd.isAlive()); + + LocalRepoKeyStore localRepoKeyStore = LocalRepoKeyStore.get(context); + Certificate localCert = localRepoKeyStore.getCertificate(); + String signingCert = Hasher.hex(localCert); + assertFalse(TextUtils.isEmpty(signingCert)); + assertFalse(TextUtils.isEmpty(Utils.calcFingerprint(localCert))); + + Repo repoToDelete = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.address); + while (repoToDelete != null) { + Log.d(TAG, "Removing old test swap repo matching this one: " + repoToDelete.address); + RepoProvider.Helper.remove(context, repoToDelete.getId()); + repoToDelete = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.address); + } + + ContentValues values = new ContentValues(4); + values.put(Schema.RepoTable.Cols.SIGNING_CERT, signingCert); + values.put(Schema.RepoTable.Cols.ADDRESS, FDroidApp.repo.address); + values.put(Schema.RepoTable.Cols.NAME, FDroidApp.repo.name); + values.put(Schema.RepoTable.Cols.IS_SWAP, true); + final String lastEtag = UUID.randomUUID().toString(); + values.put(Schema.RepoTable.Cols.LAST_ETAG, lastEtag); + RepoProvider.Helper.insert(context, values); + Repo repo = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.address); + assertTrue(repo.isSwap); + assertNotEquals(-1, repo.getId()); + assertTrue(repo.name.startsWith(FDroidApp.repo.name)); + assertEquals(lastEtag, repo.lastetag); + assertNull(repo.lastUpdated); + + assertTrue(isPortInUse(FDroidApp.ipAddressString, FDroidApp.port)); + Thread.sleep(100); + IndexUpdater updater = new IndexUpdater(context, repo); + updater.update(); + assertTrue(updater.hasChanged()); + + repo = RepoProvider.Helper.findByAddress(context, FDroidApp.repo.address); + final Date lastUpdated = repo.lastUpdated; + assertTrue("repo lastUpdated should be updated", new Date(2019, 5, 13).compareTo(repo.lastUpdated) > 0); + + App app = AppProvider.Helper.findSpecificApp(context.getContentResolver(), + context.getPackageName(), repo.getId()); + assertEquals(context.getPackageName(), app.packageName); + + List apks = ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL); + assertEquals(1, apks.size()); + for (Apk apk : apks) { + Log.i(TAG, "Apk: " + apk); + assertEquals(context.getPackageName(), apk.packageName); + assertEquals(BuildConfig.VERSION_NAME, apk.versionName); + assertEquals(BuildConfig.VERSION_CODE, apk.versionCode); + assertEquals(app.repoId, apk.repoId); + } + + Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + List resolveInfoList = context.getPackageManager().queryIntentActivities(mainIntent, 0); + HashSet packageNames = new HashSet<>(); + for (ResolveInfo resolveInfo : resolveInfoList) { + if (!isSystemPackage(resolveInfo)) { + Log.i(TAG, "resolveInfo: " + resolveInfo); + packageNames.add(resolveInfo.activityInfo.packageName); + } + } + LocalRepoService.runProcess(context, packageNames.toArray(new String[0])); + + updater = new IndexUpdater(context, repo); + updater.update(); + assertTrue(updater.hasChanged()); + assertTrue("repo lastUpdated should be updated", lastUpdated.compareTo(repo.lastUpdated) < 0); + + for (String packageName : packageNames) { + assertNotNull(ApkProvider.Helper.findByPackageName(context, packageName)); + } + } finally { + if (localHttpd != null) { + localHttpd.stop(); + } + } + if (localHttpd != null) { + assertFalse(localHttpd.isAlive()); + } + } + + private boolean isPortInUse(String host, int port) { + boolean result = false; + + try { + (new Socket(host, port)).close(); + result = true; + } catch (IOException e) { + // Could not connect. + e.printStackTrace(); + } + return result; + } + + private boolean isSystemPackage(ResolveInfo resolveInfo) { + return (resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } +} diff --git a/app/src/full/AndroidManifest.xml b/app/src/full/AndroidManifest.xml index 70c2bbe0f..b4cfe4e2f 100644 --- a/app/src/full/AndroidManifest.xml +++ b/app/src/full/AndroidManifest.xml @@ -80,6 +80,9 @@ + diff --git a/app/src/full/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java b/app/src/full/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java index 1f818c7fb..12ee3f23d 100644 --- a/app/src/full/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java +++ b/app/src/full/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java @@ -21,6 +21,8 @@ import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.App; +import org.fdroid.fdroid.data.InstalledApp; +import org.fdroid.fdroid.data.InstalledAppProvider; import org.fdroid.fdroid.data.SanitizedFile; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; @@ -246,6 +248,10 @@ public final class LocalRepoManager { return xmlIndexJar; } + public File getWebRoot() { + return webRoot; + } + public void deleteRepo() { deleteContents(repoDir); } diff --git a/app/src/full/java/org/fdroid/fdroid/localrepo/LocalRepoService.java b/app/src/full/java/org/fdroid/fdroid/localrepo/LocalRepoService.java new file mode 100644 index 000000000..554af9c9a --- /dev/null +++ b/app/src/full/java/org/fdroid/fdroid/localrepo/LocalRepoService.java @@ -0,0 +1,163 @@ +package org.fdroid.fdroid.localrepo; + +import android.app.IntentService; +import android.content.Context; +import android.content.Intent; +import android.support.v4.content.LocalBroadcastManager; +import org.fdroid.fdroid.FDroidApp; +import org.fdroid.fdroid.R; +import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import static org.fdroid.fdroid.views.swap.SwapWorkflowActivity.PrepareSwapRepo.EXTRA_TYPE; +import static org.fdroid.fdroid.views.swap.SwapWorkflowActivity.PrepareSwapRepo.TYPE_COMPLETE; +import static org.fdroid.fdroid.views.swap.SwapWorkflowActivity.PrepareSwapRepo.TYPE_ERROR; +import static org.fdroid.fdroid.views.swap.SwapWorkflowActivity.PrepareSwapRepo.TYPE_STATUS; + +/** + * Handles setting up and generating the local repo used to swap apps, including + * the {@code index.jar}, the symlinks to the shared APKs, etc. + *

+ * The work is done in a {@link Thread} so that new incoming {@code Intents} + * are not blocked by processing. A new {@code Intent} immediately nullifies + * the current state because it means the user has chosen a different set of + * apps. That is also enforced here since new {@code Intent}s with the same + * {@link Set} of apps as the current one are ignored. Having the + * {@code Thread} also makes it easy to kill work that is in progress. + */ +public class LocalRepoService extends IntentService { + public static final String TAG = "LocalRepoService"; + + public static final String ACTION_PROGRESS = "org.fdroid.fdroid.localrepo.LocalRepoService.action.PROGRESS"; + public static final String ACTION_COMPLETE = "org.fdroid.fdroid.localrepo.LocalRepoService.action.COMPLETE"; + public static final String ACTION_ERROR = "org.fdroid.fdroid.localrepo.LocalRepoService.action.ERROR"; + + public static final String EXTRA_MESSAGE = "org.fdroid.fdroid.localrepo.LocalRepoService.extra.MESSAGE"; + + public static final String ACTION_CREATE = "org.fdroid.fdroid.localrepo.action.CREATE"; + public static final String EXTRA_PACKAGE_NAMES = "org.fdroid.fdroid.localrepo.extra.PACKAGE_NAMES"; + + private final HashSet selectedApps = new HashSet<>(); + + private GenerateLocalRepoThread thread; + + public LocalRepoService() { + super("LocalRepoService"); + } + + /** + * Creates a skeleton swap repo with only F-Droid itself in it + */ + public static void create(Context context) { + create(context, Collections.singleton(context.getPackageName())); + } + + /** + * Sets up the local repo with the included {@code packageNames} + */ + public static void create(Context context, Set packageNames) { + Intent intent = new Intent(context, LocalRepoService.class); + intent.setAction(ACTION_CREATE); + intent.putExtra(EXTRA_PACKAGE_NAMES, packageNames.toArray(new String[0])); + context.startService(intent); + } + + @Override + protected void onHandleIntent(Intent intent) { + String[] packageNames = intent.getStringArrayExtra(EXTRA_PACKAGE_NAMES); + if (packageNames == null || packageNames.length == 0) { + Utils.debugLog(TAG, "no packageNames found, quiting"); + return; + } + + boolean changed = Collections.addAll(selectedApps, packageNames); + if (!changed) { + Utils.debugLog(TAG, "packageNames list unchanged, quiting"); + return; + } + + if (thread != null) { + thread.interrupt(); + } + thread = new GenerateLocalRepoThread(); + thread.start(); + } + + private class GenerateLocalRepoThread extends Thread { + private static final String TAG = "GenerateLocalRepoThread"; + + @Override + public void run() { + android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST); + runProcess(LocalRepoService.this, selectedApps); + } + } + + public static void runProcess(Context context, Set selectedApps) { + try { + final LocalRepoManager lrm = LocalRepoManager.get(context); + broadcast(context, ACTION_PROGRESS, R.string.deleting_repo); + lrm.deleteRepo(); + for (String app : selectedApps) { + broadcast(context, ACTION_PROGRESS, context.getString(R.string.adding_apks_format, app)); + lrm.addApp(context, app); + } + String urlString = Utils.getSharingUri(FDroidApp.repo).toString(); + lrm.writeIndexPage(urlString); + broadcast(context, ACTION_PROGRESS, R.string.writing_index_jar); + lrm.writeIndexJar(); + broadcast(context, ACTION_PROGRESS, R.string.linking_apks); + lrm.copyApksToRepo(); + broadcast(context, ACTION_PROGRESS, R.string.copying_icons); + // run the icon copy without progress, its not a blocker + new Thread() { + @Override + public void run() { + android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST); + lrm.copyIconsToRepo(); + } + }.start(); + + broadcast(context, ACTION_COMPLETE, null); + } catch (IOException | XmlPullParserException | LocalRepoKeyStore.InitException e) { + broadcast(context, ACTION_ERROR, e.getLocalizedMessage()); + e.printStackTrace(); + } + } + + /** + * Translate Android style broadcast {@link Intent}s to {@code PrepareSwapRepo} + */ + static void broadcast(Context context, String action, String message) { + Intent intent = new Intent(context, SwapWorkflowActivity.class); + intent.setAction(SwapWorkflowActivity.PrepareSwapRepo.ACTION); + switch (action) { + case ACTION_PROGRESS: + intent.putExtra(EXTRA_TYPE, TYPE_STATUS); + break; + case ACTION_COMPLETE: + intent.putExtra(EXTRA_TYPE, TYPE_COMPLETE); + break; + case ACTION_ERROR: + intent.putExtra(EXTRA_TYPE, TYPE_ERROR); + break; + default: + throw new IllegalArgumentException("unsupported action"); + } + if (message != null) { + Utils.debugLog(TAG, "Preparing swap: " + message); + intent.putExtra(EXTRA_MESSAGE, message); + } + LocalBroadcastManager.getInstance(context).sendBroadcast(intent); + } + + static void broadcast(Context context, String action, int resId) { + broadcast(context, action, context.getString(resId)); + } +} diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index ee92c4dca..d40a35ae1 100644 --- a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -12,7 +12,6 @@ import android.content.Intent; import android.content.ServiceConnection; import android.net.Uri; import android.net.wifi.WifiManager; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.IBinder; @@ -51,19 +50,16 @@ import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.NewRepoConfig; import org.fdroid.fdroid.installer.InstallManagerService; import org.fdroid.fdroid.installer.Installer; -import org.fdroid.fdroid.localrepo.LocalRepoManager; +import org.fdroid.fdroid.localrepo.LocalRepoService; import org.fdroid.fdroid.localrepo.SwapService; import org.fdroid.fdroid.localrepo.SwapView; import org.fdroid.fdroid.localrepo.peers.Peer; import org.fdroid.fdroid.net.BluetoothDownloader; import org.fdroid.fdroid.net.HttpDownloader; -import java.util.Arrays; import java.util.Date; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; import java.util.Timer; import java.util.TimerTask; @@ -103,7 +99,6 @@ public class SwapWorkflowActivity extends AppCompatActivity { private Toolbar toolbar; private SwapView currentView; private boolean hasPreparedLocalRepo; - private PrepareSwapRepo updateSwappableAppsTask; private NewRepoConfig confirmSwapConfig; private LocalBroadcastManager localBroadcastManager; private WifiManager wifiManager; @@ -504,12 +499,7 @@ public class SwapWorkflowActivity extends AppCompatActivity { // as we are starting over now. getService().swapWith(null); - if (!getService().isEnabled()) { - if (!LocalRepoManager.get(this).getIndexJar().exists()) { - Utils.debugLog(TAG, "Preparing initial repo with only F-Droid, until we have allowed the user to configure their own repo."); - new PrepareInitialSwapRepo().execute(); - } - } + LocalRepoService.create(this); inflateSwapView(R.layout.swap_start_swap); } @@ -591,30 +581,34 @@ public class SwapWorkflowActivity extends AppCompatActivity { ((FDroidApp) getApplication()).sendViaBluetooth(this, Activity.RESULT_OK, BuildConfig.APPLICATION_ID); } - // TODO: Figure out whether they have changed since last time UpdateAsyncTask was run. - // If the local repo is running, then we can ask it what apps it is swapping and compare with that. - // Otherwise, probably will need to scan the file system. + /** + * TODO: Figure out whether they have changed since last time LocalRepoService + * was run. If the local repo is running, then we can ask it what apps it is + * swapping and compare with that. Otherwise, probably will need to scan the + * file system. + */ public void onAppsSelected() { - if (updateSwappableAppsTask == null && !hasPreparedLocalRepo) { - updateSwappableAppsTask = new PrepareSwapRepo(getService().getAppsToSwap()); - updateSwappableAppsTask.execute(); + if (hasPreparedLocalRepo) { + onLocalRepoPrepared(); + } else { + LocalRepoService.create(this, getService().getAppsToSwap()); getService().setCurrentView(R.layout.swap_connecting); inflateSwapView(R.layout.swap_connecting); - } else { - onLocalRepoPrepared(); } } /** * Once the UpdateAsyncTask has finished preparing our repository index, we can * show the next screen to the user. This will be one of two things: - * * If we directly selected a peer to swap with initially, we will skip straight to getting - * the list of apps from that device. - * * Alternatively, if we didn't have a person to connect to, and instead clicked "Scan QR Code", - * then we want to show a QR code or NFC dialog. + *

    + *
  1. If we directly selected a peer to swap with initially, we will skip straight to getting + * the list of apps from that device.
  2. + *
  3. Alternatively, if we didn't have a person to connect to, and instead clicked "Scan QR Code", + * then we want to show a QR code or NFC dialog.
  4. + *
*/ public void onLocalRepoPrepared() { - updateSwappableAppsTask = null; + // TODO ditch this, use a message from LocalRepoService. Maybe? hasPreparedLocalRepo = true; if (getService().isConnectingWithPeer()) { startSwappingWithPeer(); @@ -637,6 +631,7 @@ public class SwapWorkflowActivity extends AppCompatActivity { // during the wifi qr code being shown too. boolean nfcMessageReady = NfcHelper.setPushMessage(this, Utils.getSharingUri(FDroidApp.repo)); + // TODO move all swap-specific preferences to a SharedPreferences instance for SwapWorkflowActivity if (Preferences.get().showNfcDuringSwap() && nfcMessageReady) { inflateSwapView(R.layout.swap_nfc); return true; @@ -779,82 +774,13 @@ public class SwapWorkflowActivity extends AppCompatActivity { service.getBluetoothSwap().startInBackground(); // TODO replace with Intent to SwapService } - class PrepareInitialSwapRepo extends PrepareSwapRepo { - PrepareInitialSwapRepo() { - super(new HashSet<>(Arrays.asList(new String[]{BuildConfig.APPLICATION_ID}))); - } - } - - class PrepareSwapRepo extends AsyncTask { - + public class PrepareSwapRepo { public static final String ACTION = "PrepareSwapRepo.Action"; public static final String EXTRA_MESSAGE = "PrepareSwapRepo.Status.Message"; public static final String EXTRA_TYPE = "PrepareSwapRepo.Action.Type"; public static final int TYPE_STATUS = 0; public static final int TYPE_COMPLETE = 1; public static final int TYPE_ERROR = 2; - - @NonNull - protected final Set selectedApps; - - @NonNull - protected final Uri sharingUri; - - @NonNull - protected final Context context; - - PrepareSwapRepo(@NonNull Set apps) { - context = SwapWorkflowActivity.this; - selectedApps = apps; - sharingUri = Utils.getSharingUri(FDroidApp.repo); - } - - private void broadcast(int type) { - broadcast(type, null); - } - - private void broadcast(int type, String message) { - Intent intent = new Intent(ACTION); - intent.putExtra(EXTRA_TYPE, type); - if (message != null) { - Utils.debugLog(TAG, "Preparing swap: " + message); - intent.putExtra(EXTRA_MESSAGE, message); - } - LocalBroadcastManager.getInstance(SwapWorkflowActivity.this).sendBroadcast(intent); - } - - @Override - protected Void doInBackground(Void... params) { - try { - final LocalRepoManager lrm = LocalRepoManager.get(context); - broadcast(TYPE_STATUS, getString(R.string.deleting_repo)); - lrm.deleteRepo(); - for (String app : selectedApps) { - broadcast(TYPE_STATUS, String.format(getString(R.string.adding_apks_format), app)); - lrm.addApp(context, app); - } - lrm.writeIndexPage(sharingUri.toString()); - broadcast(TYPE_STATUS, getString(R.string.writing_index_jar)); - lrm.writeIndexJar(); - broadcast(TYPE_STATUS, getString(R.string.linking_apks)); - lrm.copyApksToRepo(); - broadcast(TYPE_STATUS, getString(R.string.copying_icons)); - // run the icon copy without progress, its not a blocker - new Thread() { - @Override - public void run() { - android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND); - lrm.copyIconsToRepo(); - } - }.start(); - - broadcast(TYPE_COMPLETE); - } catch (Exception e) { - broadcast(TYPE_ERROR); - Log.e(TAG, "", e); - } - return null; - } } /** diff --git a/app/src/testFull/java/org/fdroid/fdroid/data/ShadowApp.java b/app/src/testFull/java/org/fdroid/fdroid/data/ShadowApp.java new file mode 100644 index 000000000..a9534989f --- /dev/null +++ b/app/src/testFull/java/org/fdroid/fdroid/data/ShadowApp.java @@ -0,0 +1,14 @@ +package org.fdroid.fdroid.data; + +import android.content.Context; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(App.class) +public class ShadowApp extends ValueObject { + + @Implementation + protected static int[] getMinTargetMaxSdkVersions(Context context, String packageName) { + return new int[]{10, 23, Apk.SDK_VERSION_MAX_VALUE}; + } +} diff --git a/app/src/testFull/java/org/fdroid/fdroid/updater/SwapRepoTest.java b/app/src/testFull/java/org/fdroid/fdroid/updater/SwapRepoTest.java new file mode 100644 index 000000000..1436c5040 --- /dev/null +++ b/app/src/testFull/java/org/fdroid/fdroid/updater/SwapRepoTest.java @@ -0,0 +1,187 @@ +package org.fdroid.fdroid.updater; + +import android.content.ContentResolver; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.text.TextUtils; +import org.apache.commons.net.util.SubnetUtils; +import org.fdroid.fdroid.FDroidApp; +import org.fdroid.fdroid.Hasher; +import org.fdroid.fdroid.IndexUpdater; +import org.fdroid.fdroid.Preferences; +import org.fdroid.fdroid.TestUtils; +import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.data.Apk; +import org.fdroid.fdroid.data.ApkProvider; +import org.fdroid.fdroid.data.AppProvider; +import org.fdroid.fdroid.data.DBHelper; +import org.fdroid.fdroid.data.Repo; +import org.fdroid.fdroid.data.RepoProvider; +import org.fdroid.fdroid.data.Schema; +import org.fdroid.fdroid.data.ShadowApp; +import org.fdroid.fdroid.data.TempAppProvider; +import org.fdroid.fdroid.localrepo.LocalRepoKeyStore; +import org.fdroid.fdroid.localrepo.LocalRepoManager; +import org.fdroid.fdroid.localrepo.LocalRepoService; +import org.fdroid.fdroid.net.LocalHTTPD; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowContentResolver; +import org.robolectric.shadows.ShadowLog; +import org.robolectric.shadows.ShadowPackageManager; + +import java.io.File; +import java.io.IOException; +import java.security.cert.Certificate; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.robolectric.Shadows.shadowOf; + +/** + * This test almost works, it needs to have the {@link android.content.ContentProvider} + * and {@link ContentResolver} stuff worked out. It currently fails as + * {@code updater.update()}. + */ +@Ignore +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowApp.class) +public class SwapRepoTest { + + private LocalHTTPD localHttpd; + + protected ShadowContentResolver shadowContentResolver; + protected ContentResolver contentResolver; + protected ContextWrapper context; + + @Before + public void setUp() { + ShadowLog.stream = System.out; + + contentResolver = RuntimeEnvironment.application.getContentResolver(); + shadowContentResolver = Shadows.shadowOf(contentResolver); + context = new ContextWrapper(RuntimeEnvironment.application.getApplicationContext()) { + @Override + public ContentResolver getContentResolver() { + return contentResolver; + } + }; + + TestUtils.registerContentProvider(ApkProvider.getAuthority(), ApkProvider.class); + TestUtils.registerContentProvider(AppProvider.getAuthority(), AppProvider.class); + TestUtils.registerContentProvider(RepoProvider.getAuthority(), RepoProvider.class); + TestUtils.registerContentProvider(TempAppProvider.getAuthority(), TempAppProvider.class); + + Preferences.setupForTests(context); + } + + @After + public final void tearDownBase() { + DBHelper.clearDbHelperSingleton(); + } + + /** + * @see org.fdroid.fdroid.net.WifiStateChangeService.WifiInfoThread#run() + */ + @Test + public void testSwap() + throws IOException, LocalRepoKeyStore.InitException, IndexUpdater.UpdateException, InterruptedException { + + PackageManager packageManager = context.getPackageManager(); + ShadowPackageManager shadowPackageManager = shadowOf(packageManager); + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.flags = 0; + appInfo.packageName = context.getPackageName(); + appInfo.minSdkVersion = 10; + appInfo.targetSdkVersion = 23; + appInfo.sourceDir = getClass().getClassLoader().getResource("F-Droid.apk").getPath(); + appInfo.publicSourceDir = getClass().getClassLoader().getResource("F-Droid.apk").getPath(); + System.out.println("appInfo.sourceDir " + appInfo.sourceDir); + appInfo.name = "F-Droid"; + + PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = appInfo.packageName; + packageInfo.applicationInfo = appInfo; + packageInfo.versionCode = 1002001; + packageInfo.versionName = "1.2-fake"; + shadowPackageManager.addPackage(packageInfo); + + try { + FDroidApp.initWifiSettings(); + FDroidApp.ipAddressString = "127.0.0.1"; + FDroidApp.subnetInfo = new SubnetUtils("127.0.0.0/8").getInfo(); + FDroidApp.repo.name = "test"; + FDroidApp.repo.address = "http://" + FDroidApp.ipAddressString + ":" + FDroidApp.port + "/fdroid/repo"; + + LocalRepoService.runProcess(context, new String[]{context.getPackageName()}); + File indexJarFile = LocalRepoManager.get(context).getIndexJar(); + System.out.println("indexJarFile:" + indexJarFile); + assertTrue(indexJarFile.isFile()); + + localHttpd = new LocalHTTPD( + context, + FDroidApp.ipAddressString, + FDroidApp.port, + LocalRepoManager.get(context).getWebRoot(), + false); + localHttpd.start(); + Thread.sleep(100); // give the server some tine to start. + assertTrue(localHttpd.isAlive()); + + LocalRepoKeyStore localRepoKeyStore = LocalRepoKeyStore.get(context); + Certificate localCert = localRepoKeyStore.getCertificate(); + String signingCert = Hasher.hex(localCert); + assertFalse(TextUtils.isEmpty(signingCert)); + assertFalse(TextUtils.isEmpty(Utils.calcFingerprint(localCert))); + + Repo repo = MultiIndexUpdaterTest.createRepo(FDroidApp.repo.name, FDroidApp.repo.address, + context, signingCert); + IndexUpdater updater = new IndexUpdater(context, repo); + updater.update(); + assertTrue(updater.hasChanged()); + updater.processDownloadedFile(indexJarFile); + + boolean foundRepo = false; + for (Repo repoFromDb : RepoProvider.Helper.all(context)) { + if (TextUtils.equals(repo.address, repoFromDb.address)) { + foundRepo = true; + repo = repoFromDb; + } + } + assertTrue(foundRepo); + + assertNotEquals(-1, repo.getId()); + List apks = ApkProvider.Helper.findByRepo(context, repo, Schema.ApkTable.Cols.ALL); + assertEquals(1, apks.size()); + for (Apk apk : apks) { + System.out.println(apk); + } + //MultiIndexUpdaterTest.assertApksExist(apks, context.getPackageName(), new int[]{BuildConfig.VERSION_CODE}); + Thread.sleep(10000); + } finally { + if (localHttpd != null) { + localHttpd.stop(); + } + } + } + + class TestLocalRepoService extends LocalRepoService { + @Override + protected void onHandleIntent(Intent intent) { + super.onHandleIntent(intent); + } + } +} \ No newline at end of file From ea3b47f705456f1cef57996a52625a6990e87de3 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Tue, 14 May 2019 23:47:24 +0200 Subject: [PATCH 02/41] purge CacheSwapAppsService in favor of InstalledAppProvider The most expensive part of this whole process is calculating the hash of the whole APK. InstalledAppProvider already caches that, and the rest is OK to query. If any particular part of the query is expensive, it could also be moved to InstalledAppProviderService. --- app/src/full/AndroidManifest.xml | 3 - .../localrepo/CacheSwapAppsService.java | 85 ------------------- .../fdroid/localrepo/LocalRepoManager.java | 8 +- .../fdroid/fdroid/localrepo/SwapService.java | 17 ---- .../main/java/org/fdroid/fdroid/data/App.java | 7 +- 5 files changed, 8 insertions(+), 112 deletions(-) delete mode 100644 app/src/full/java/org/fdroid/fdroid/localrepo/CacheSwapAppsService.java diff --git a/app/src/full/AndroidManifest.xml b/app/src/full/AndroidManifest.xml index b4cfe4e2f..ed877de77 100644 --- a/app/src/full/AndroidManifest.xml +++ b/app/src/full/AndroidManifest.xml @@ -77,9 +77,6 @@ android:exported="false"/> - diff --git a/app/src/full/java/org/fdroid/fdroid/localrepo/CacheSwapAppsService.java b/app/src/full/java/org/fdroid/fdroid/localrepo/CacheSwapAppsService.java deleted file mode 100644 index f6452c1eb..000000000 --- a/app/src/full/java/org/fdroid/fdroid/localrepo/CacheSwapAppsService.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.fdroid.fdroid.localrepo; - -import android.app.IntentService; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import org.apache.commons.io.FileUtils; -import org.fdroid.fdroid.FDroidApp; -import org.fdroid.fdroid.Utils; -import org.fdroid.fdroid.data.App; - -import java.io.File; -import java.io.IOException; -import java.security.cert.CertificateEncodingException; - -/** - * An {@link IntentService} subclass for generating cached info about the installed APKs - * which are available for swapping. It does not cache system apps, since those are - * rarely swapped. This is meant to start running when {@link SwapService} starts. - *

- * This could probably be replaced by {@link org.fdroid.fdroid.data.InstalledAppProvider} - * if that contained all of the info to generate complete {@link App} and - * {@link org.fdroid.fdroid.data.Apk} instances. - */ -public class CacheSwapAppsService extends IntentService { - private static final String TAG = "CacheSwapAppsService"; - - private static final String ACTION_PARSE_APP = "org.fdroid.fdroid.localrepo.action.PARSE_APP"; - - public CacheSwapAppsService() { - super("CacheSwapAppsService"); - } - - /** - * Parse the locally installed APK for {@code packageName} and save its XML - * to the APK XML cache. - */ - private static void parseApp(Context context, String packageName) { - Intent intent = new Intent(); - intent.setData(Utils.getPackageUri(packageName)); - intent.setClass(context, CacheSwapAppsService.class); - intent.setAction(ACTION_PARSE_APP); - context.startService(intent); - } - - /** - * Parse all of the locally installed APKs into a memory cache, starting - * with the currently selected apps. APKs that are already parsed in the - * {@code index.jar} file will be read from that file. - */ - public static void startCaching(Context context) { - File indexJarFile = LocalRepoManager.get(context).getIndexJar(); - PackageManager pm = context.getPackageManager(); - for (ApplicationInfo applicationInfo : pm.getInstalledApplications(0)) { - if (applicationInfo.publicSourceDir.startsWith(FDroidApp.SYSTEM_DIR_NAME)) { - continue; - } - if (!indexJarFile.exists() - || FileUtils.isFileNewer(new File(applicationInfo.sourceDir), indexJarFile)) { - parseApp(context, applicationInfo.packageName); - } - } - } - - @Override - protected void onHandleIntent(Intent intent) { - android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST); - if (intent == null || !ACTION_PARSE_APP.equals(intent.getAction())) { - Utils.debugLog(TAG, "received bad Intent: " + intent); - return; - } - - try { - PackageManager pm = getPackageManager(); - String packageName = intent.getData().getSchemeSpecificPart(); - App app = App.getInstance(this, pm, packageName); - if (app != null) { - SwapService.putAppInCache(packageName, app); - } - } catch (CertificateEncodingException | IOException | PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } - } -} diff --git a/app/src/full/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java b/app/src/full/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java index 12ee3f23d..7bd4063f1 100644 --- a/app/src/full/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java +++ b/app/src/full/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java @@ -276,12 +276,10 @@ public final class LocalRepoManager { } public void addApp(Context context, String packageName) { - App app; + App app = null; try { - app = SwapService.getAppFromCache(packageName); - if (app == null) { - app = App.getInstance(context.getApplicationContext(), pm, packageName); - } + InstalledApp installedApp = InstalledAppProvider.Helper.findByPackageName(context, packageName); + app = App.getInstance(context, pm, installedApp, packageName); if (app == null || !app.isValid()) { return; } diff --git a/app/src/full/java/org/fdroid/fdroid/localrepo/SwapService.java b/app/src/full/java/org/fdroid/fdroid/localrepo/SwapService.java index 332cea135..2d1848088 100644 --- a/app/src/full/java/org/fdroid/fdroid/localrepo/SwapService.java +++ b/app/src/full/java/org/fdroid/fdroid/localrepo/SwapService.java @@ -27,7 +27,6 @@ import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.R; import org.fdroid.fdroid.UpdateService; import org.fdroid.fdroid.Utils; -import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.Schema; @@ -53,7 +52,6 @@ import java.util.HashSet; import java.util.Set; import java.util.Timer; import java.util.TimerTask; -import java.util.concurrent.ConcurrentHashMap; /** * Central service which manages all of the different moving parts of swap which are required @@ -74,11 +72,6 @@ public class SwapService extends Service { @NonNull private final Set appsToSwap = new HashSet<>(); - /** - * A cache of parsed APKs from the file system. - */ - private static final ConcurrentHashMap INSTALLED_APPS = new ConcurrentHashMap<>(); - private static SharedPreferences swapPreferences; private static BluetoothAdapter bluetoothAdapter; private static WifiManager wifiManager; @@ -88,14 +81,6 @@ public class SwapService extends Service { context.stopService(intent); } - static App getAppFromCache(String packageName) { - return INSTALLED_APPS.get(packageName); - } - - static void putAppInCache(String packageName, @NonNull App app) { - INSTALLED_APPS.put(packageName, app); - } - // ========================================================== // Search for peers to swap // ========================================================== @@ -447,8 +432,6 @@ public class SwapService extends Service { deleteAllSwapRepos(); - CacheSwapAppsService.startCaching(this); - swapPreferences = getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); diff --git a/app/src/main/java/org/fdroid/fdroid/data/App.java b/app/src/main/java/org/fdroid/fdroid/data/App.java index c7badbb29..c13b53990 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/App.java +++ b/app/src/main/java/org/fdroid/fdroid/data/App.java @@ -385,13 +385,16 @@ public class App extends ValueObject implements Comparable, Parcelable { * exists. */ @Nullable - public static App getInstance(Context context, PackageManager pm, String packageName) + public static App getInstance(Context context, PackageManager pm, InstalledApp installedApp, String packageName) throws CertificateEncodingException, IOException, PackageManager.NameNotFoundException { App app = new App(); PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); SanitizedFile apkFile = SanitizedFile.knownSanitized(packageInfo.applicationInfo.publicSourceDir); app.installedApk = new Apk(); - if (apkFile.canRead()) { + if (installedApp != null) { + app.installedApk.hashType = installedApp.getHashType(); + app.installedApk.hash = installedApp.getHash(); + } else if (apkFile.canRead()) { String hashType = "sha256"; String hash = Utils.getBinaryHash(apkFile, hashType); if (TextUtils.isEmpty(hash)) { From 69ce8dbe8c4d60311f2334c73af3bdfa1e897ef3 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 15 May 2019 11:50:35 +0200 Subject: [PATCH 03/41] move all WiFi/QR logic to Presenter (SwapWorkflowActivity) --- .../fdroid/views/swap/SendFDroidView.java | 70 ------------- .../views/swap/SwapWorkflowActivity.java | 92 +++++++++++++++++ .../fdroid/fdroid/views/swap/WifiQrView.java | 99 ------------------- 3 files changed, 92 insertions(+), 169 deletions(-) diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/SendFDroidView.java b/app/src/full/java/org/fdroid/fdroid/views/swap/SendFDroidView.java index 30ba74da7..307533028 100644 --- a/app/src/full/java/org/fdroid/fdroid/views/swap/SendFDroidView.java +++ b/app/src/full/java/org/fdroid/fdroid/views/swap/SendFDroidView.java @@ -1,27 +1,12 @@ package org.fdroid.fdroid.views.swap; -import android.annotation.SuppressLint; import android.annotation.TargetApi; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.graphics.LightingColorFilter; -import android.support.v4.content.LocalBroadcastManager; -import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; -import org.fdroid.fdroid.FDroidApp; -import org.fdroid.fdroid.Preferences; -import org.fdroid.fdroid.QrGenAsyncTask; import org.fdroid.fdroid.R; -import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.localrepo.SwapView; -import org.fdroid.fdroid.net.WifiStateChangeService; -import org.fdroid.fdroid.views.swap.device.camera.CameraCharacteristicsChecker; public class SendFDroidView extends SwapView { @@ -47,13 +32,6 @@ public class SendFDroidView extends SwapView { @Override protected void onFinishInflate() { super.onFinishInflate(); - setUIFromWifi(); - setUpWarningMessageQrScan(); - - ImageView qrImage = (ImageView) findViewById(R.id.wifi_qr_code); - - // Replace all blacks with the background blue. - qrImage.setColorFilter(new LightingColorFilter(0xffffffff, getResources().getColor(R.color.swap_blue))); Button useBluetooth = (Button) findViewById(R.id.btn_use_bluetooth); useBluetooth.setOnClickListener(new Button.OnClickListener() { @@ -63,53 +41,5 @@ public class SendFDroidView extends SwapView { getActivity().sendFDroidBluetooth(); } }); - - LocalBroadcastManager.getInstance(getActivity()).registerReceiver( - onWifiStateChanged, new IntentFilter(WifiStateChangeService.BROADCAST)); } - - private void setUpWarningMessageQrScan() { - final View qrWarningMessage = findViewById(R.id.warning_qr_scanner); - final boolean hasAutofocus = CameraCharacteristicsChecker.getInstance(getContext()).hasAutofocus(); - final int visiblity = hasAutofocus ? GONE : VISIBLE; - qrWarningMessage.setVisibility(visiblity); - } - - - /** - * Remove relevant listeners/receivers/etc so that they do not receive and process events - * when this view is not in use. - */ - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(onWifiStateChanged); - } - - @SuppressLint("HardwareIds") - private void setUIFromWifi() { - if (TextUtils.isEmpty(FDroidApp.repo.address)) { - return; - } - - String scheme = Preferences.get().isLocalRepoHttpsEnabled() ? "https://" : "http://"; - - // the fingerprint is not useful on the button label - String qrUriString = scheme + FDroidApp.ipAddressString + ":" + FDroidApp.port; - TextView ipAddressView = (TextView) findViewById(R.id.device_ip_address); - ipAddressView.setText(qrUriString); - - Utils.debugLog(TAG, "Encoded swap URI in QR Code: " + qrUriString); - new QrGenAsyncTask(getActivity(), R.id.wifi_qr_code).execute(qrUriString); - - } - - private final BroadcastReceiver onWifiStateChanged = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - setUIFromWifi(); - } - }; - } diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index d40a35ae1..7e278fb00 100644 --- a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -9,7 +9,9 @@ import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.IntentFilter; import android.content.ServiceConnection; +import android.graphics.LightingColorFilter; import android.net.Uri; import android.net.wifi.WifiManager; import android.os.Build; @@ -34,6 +36,7 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import cc.mvdan.accesspoint.WifiApControl; @@ -43,6 +46,7 @@ import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.NfcHelper; import org.fdroid.fdroid.Preferences; +import org.fdroid.fdroid.QrGenAsyncTask; import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Apk; @@ -56,10 +60,14 @@ import org.fdroid.fdroid.localrepo.SwapView; import org.fdroid.fdroid.localrepo.peers.Peer; import org.fdroid.fdroid.net.BluetoothDownloader; import org.fdroid.fdroid.net.HttpDownloader; +import org.fdroid.fdroid.net.WifiStateChangeService; +import org.fdroid.fdroid.views.swap.device.camera.CameraCharacteristicsChecker; import java.util.Date; import java.util.HashMap; +import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.Timer; import java.util.TimerTask; @@ -328,10 +336,20 @@ public class SwapWorkflowActivity extends AppCompatActivity { protected void onResume() { super.onResume(); + localBroadcastManager.registerReceiver(onWifiStateChanged, + new IntentFilter(WifiStateChangeService.BROADCAST)); + checkIncomingIntent(); showRelevantView(); } + @Override + protected void onPause() { + super.onPause(); + + localBroadcastManager.unregisterReceiver(onWifiStateChanged); + } + /** * Check whether incoming {@link Intent} is a swap repo, and ensure that * it is a valid swap URL. The hostname can only be either an IP or @@ -486,6 +504,13 @@ public class SwapWorkflowActivity extends AppCompatActivity { container.addView(view); supportInvalidateOptionsMenu(); + switch (currentView.getLayoutResId()) { + case R.layout.swap_send_fdroid: + case R.layout.swap_wifi_qr: + setUpFromWifi(); + break; + } + return currentView; } @@ -877,4 +902,71 @@ public class SwapWorkflowActivity extends AppCompatActivity { } }; + private final BroadcastReceiver onWifiStateChanged = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + setUpFromWifi(); + } + }; + + private void setUpFromWifi() { + String scheme = Preferences.get().isLocalRepoHttpsEnabled() ? "https://" : "http://"; + + // the fingerprint is not useful on the button label + String buttonLabel = scheme + FDroidApp.ipAddressString + ":" + FDroidApp.port; + TextView ipAddressView = container.findViewById(R.id.device_ip_address); + if (ipAddressView != null) { + ipAddressView.setText(buttonLabel); + } + + String qrUriString = null; + switch (currentView.getLayoutResId()) { + case R.layout.swap_send_fdroid: + qrUriString = buttonLabel; + break; + case R.layout.swap_wifi_qr: + Uri sharingUri = Utils.getSharingUri(FDroidApp.repo); + StringBuilder qrUrlBuilder = new StringBuilder(scheme); + qrUrlBuilder.append(sharingUri.getHost()); + if (sharingUri.getPort() != 80) { + qrUrlBuilder.append(':'); + qrUrlBuilder.append(sharingUri.getPort()); + } + qrUrlBuilder.append(sharingUri.getPath()); + boolean first = true; + + Set names = sharingUri.getQueryParameterNames(); + for (String name : names) { + if (!"ssid".equals(name)) { + if (first) { + qrUrlBuilder.append('?'); + first = false; + } else { + qrUrlBuilder.append('&'); + } + qrUrlBuilder.append(name.toUpperCase(Locale.ENGLISH)); + qrUrlBuilder.append('='); + qrUrlBuilder.append(sharingUri.getQueryParameter(name).toUpperCase(Locale.ENGLISH)); + } + } + qrUriString = qrUrlBuilder.toString(); + break; + } + + ImageView qrImage = container.findViewById(R.id.wifi_qr_code); + if (qrUriString != null && qrImage != null) { + Utils.debugLog(TAG, "Encoded swap URI in QR Code: " + qrUriString); + new QrGenAsyncTask(SwapWorkflowActivity.this, R.id.wifi_qr_code).execute(qrUriString); + + // Replace all blacks with the background blue. + qrImage.setColorFilter(new LightingColorFilter(0xffffffff, getResources().getColor(R.color.swap_blue))); + + final View qrWarningMessage = container.findViewById(R.id.warning_qr_scanner); + if (CameraCharacteristicsChecker.getInstance(this).hasAutofocus()) { + qrWarningMessage.setVisibility(View.GONE); + } else { + qrWarningMessage.setVisibility(View.VISIBLE); + } + } + } } diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/WifiQrView.java b/app/src/full/java/org/fdroid/fdroid/views/swap/WifiQrView.java index ad8fadbba..cb3b741c3 100644 --- a/app/src/full/java/org/fdroid/fdroid/views/swap/WifiQrView.java +++ b/app/src/full/java/org/fdroid/fdroid/views/swap/WifiQrView.java @@ -1,30 +1,12 @@ package org.fdroid.fdroid.views.swap; import android.annotation.TargetApi; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.graphics.LightingColorFilter; -import android.net.Uri; -import android.support.v4.content.LocalBroadcastManager; -import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; -import org.fdroid.fdroid.FDroidApp; -import org.fdroid.fdroid.Preferences; -import org.fdroid.fdroid.QrGenAsyncTask; import org.fdroid.fdroid.R; -import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.localrepo.SwapView; -import org.fdroid.fdroid.net.WifiStateChangeService; -import org.fdroid.fdroid.views.swap.device.camera.CameraCharacteristicsChecker; - -import java.util.Locale; -import java.util.Set; public class WifiQrView extends SwapView { @@ -50,13 +32,6 @@ public class WifiQrView extends SwapView { @Override protected void onFinishInflate() { super.onFinishInflate(); - setUIFromWifi(); - setUpWarningMessageQrScan(); - - ImageView qrImage = (ImageView) findViewById(R.id.wifi_qr_code); - - // Replace all blacks with the background blue. - qrImage.setColorFilter(new LightingColorFilter(0xffffffff, getResources().getColor(R.color.swap_blue))); Button openQr = (Button) findViewById(R.id.btn_qr_scanner); openQr.setOnClickListener(new Button.OnClickListener() { @@ -65,79 +40,5 @@ public class WifiQrView extends SwapView { getActivity().initiateQrScan(); } }); - - LocalBroadcastManager.getInstance(getActivity()).registerReceiver( - onWifiStateChanged, new IntentFilter(WifiStateChangeService.BROADCAST)); } - - private void setUpWarningMessageQrScan() { - final View qrWarnningMessage = findViewById(R.id.warning_qr_scanner); - final boolean hasAutofocus = CameraCharacteristicsChecker.getInstance(getContext()).hasAutofocus(); - final int visiblity = hasAutofocus ? GONE : VISIBLE; - qrWarnningMessage.setVisibility(visiblity); - } - - - /** - * Remove relevant listeners/receivers/etc so that they do not receive and process events - * when this view is not in use. - */ - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(onWifiStateChanged); - } - - private void setUIFromWifi() { - - if (TextUtils.isEmpty(FDroidApp.repo.address)) { - return; - } - - String scheme = Preferences.get().isLocalRepoHttpsEnabled() ? "https://" : "http://"; - - // the fingerprint is not useful on the button label - String buttonLabel = scheme + FDroidApp.ipAddressString + ":" + FDroidApp.port; - TextView ipAddressView = (TextView) findViewById(R.id.device_ip_address); - ipAddressView.setText(buttonLabel); - - Uri sharingUri = Utils.getSharingUri(FDroidApp.repo); - StringBuilder qrUrlBuilder = new StringBuilder(scheme); - qrUrlBuilder.append(sharingUri.getHost()); - if (sharingUri.getPort() != 80) { - qrUrlBuilder.append(':'); - qrUrlBuilder.append(sharingUri.getPort()); - } - qrUrlBuilder.append(sharingUri.getPath()); - boolean first = true; - - Set names = sharingUri.getQueryParameterNames(); - for (String name : names) { - if (!"ssid".equals(name)) { - if (first) { - qrUrlBuilder.append('?'); - first = false; - } else { - qrUrlBuilder.append('&'); - } - qrUrlBuilder.append(name.toUpperCase(Locale.ENGLISH)); - qrUrlBuilder.append('='); - qrUrlBuilder.append(sharingUri.getQueryParameter(name).toUpperCase(Locale.ENGLISH)); - } - } - - String qrUriString = qrUrlBuilder.toString(); - Utils.debugLog(TAG, "Encoded swap URI in QR Code: " + qrUriString); - new QrGenAsyncTask(getActivity(), R.id.wifi_qr_code).execute(qrUriString); - - } - - private final BroadcastReceiver onWifiStateChanged = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - setUIFromWifi(); - } - }; - } From 014fb0b99d5954c4335c1d50f6147e0db6dfa216 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 15 May 2019 11:58:36 +0200 Subject: [PATCH 04/41] move WifiQrView and SendFDroidView to pure XML views This puts the logic in the "Presenter": SwapWorkflowActivity --- .../fdroid/views/swap/SendFDroidView.java | 45 ------------------- .../views/swap/SwapWorkflowActivity.java | 30 +++++++++++++ .../fdroid/fdroid/views/swap/WifiQrView.java | 44 ------------------ app/src/full/res/layout/swap_send_fdroid.xml | 4 +- app/src/full/res/layout/swap_wifi_qr.xml | 4 +- 5 files changed, 34 insertions(+), 93 deletions(-) delete mode 100644 app/src/full/java/org/fdroid/fdroid/views/swap/SendFDroidView.java delete mode 100644 app/src/full/java/org/fdroid/fdroid/views/swap/WifiQrView.java diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/SendFDroidView.java b/app/src/full/java/org/fdroid/fdroid/views/swap/SendFDroidView.java deleted file mode 100644 index 307533028..000000000 --- a/app/src/full/java/org/fdroid/fdroid/views/swap/SendFDroidView.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.fdroid.fdroid.views.swap; - -import android.annotation.TargetApi; -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.Button; -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.localrepo.SwapView; - -public class SendFDroidView extends SwapView { - - private static final String TAG = "SendFDroidView"; - - public SendFDroidView(Context context) { - super(context); - } - - public SendFDroidView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public SendFDroidView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @TargetApi(21) - public SendFDroidView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - Button useBluetooth = (Button) findViewById(R.id.btn_use_bluetooth); - useBluetooth.setOnClickListener(new Button.OnClickListener() { - @Override - public void onClick(View v) { - getActivity().showIntro(); - getActivity().sendFDroidBluetooth(); - } - }); - } -} diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index 7e278fb00..b1db868dc 100644 --- a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -36,6 +36,7 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; @@ -506,8 +507,12 @@ public class SwapWorkflowActivity extends AppCompatActivity { switch (currentView.getLayoutResId()) { case R.layout.swap_send_fdroid: + setUpFromWifi(); + setUpUseBluetoothButton(); + break; case R.layout.swap_wifi_qr: setUpFromWifi(); + setUpQrScannerButton(); break; } @@ -969,4 +974,29 @@ public class SwapWorkflowActivity extends AppCompatActivity { } } } + + private void setUpUseBluetoothButton() { + Button useBluetooth = findViewById(R.id.btn_use_bluetooth); + if (useBluetooth != null) { + useBluetooth.setOnClickListener(new Button.OnClickListener() { + @Override + public void onClick(View v) { + showIntro(); + sendFDroidBluetooth(); + } + }); + } + } + + private void setUpQrScannerButton() { + Button openQr = findViewById(R.id.btn_qr_scanner); + if (openQr != null) { + openQr.setOnClickListener(new Button.OnClickListener() { + @Override + public void onClick(View v) { + initiateQrScan(); + } + }); + } + } } diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/WifiQrView.java b/app/src/full/java/org/fdroid/fdroid/views/swap/WifiQrView.java deleted file mode 100644 index cb3b741c3..000000000 --- a/app/src/full/java/org/fdroid/fdroid/views/swap/WifiQrView.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.fdroid.fdroid.views.swap; - -import android.annotation.TargetApi; -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.Button; -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.localrepo.SwapView; - -public class WifiQrView extends SwapView { - - private static final String TAG = "WifiQrView"; - - public WifiQrView(Context context) { - super(context); - } - - public WifiQrView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public WifiQrView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @TargetApi(21) - public WifiQrView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - Button openQr = (Button) findViewById(R.id.btn_qr_scanner); - openQr.setOnClickListener(new Button.OnClickListener() { - @Override - public void onClick(View v) { - getActivity().initiateQrScan(); - } - }); - } -} diff --git a/app/src/full/res/layout/swap_send_fdroid.xml b/app/src/full/res/layout/swap_send_fdroid.xml index c86099843..d0a8fa371 100644 --- a/app/src/full/res/layout/swap_send_fdroid.xml +++ b/app/src/full/res/layout/swap_send_fdroid.xml @@ -1,6 +1,6 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/full/res/layout/swap_wifi_qr.xml b/app/src/full/res/layout/swap_wifi_qr.xml index 6678e3b84..7e46c10d1 100644 --- a/app/src/full/res/layout/swap_wifi_qr.xml +++ b/app/src/full/res/layout/swap_wifi_qr.xml @@ -1,6 +1,6 @@ - - \ No newline at end of file + \ No newline at end of file From d5f2e26ea74c1e7ddcdc80f4681d2dac0df4ebb5 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 15 May 2019 12:53:45 +0200 Subject: [PATCH 05/41] use one method everywhere for the "swap back" requests --- .../views/swap/SwapWorkflowActivity.java | 5 ++++ .../views/swap/SwapWorkflowActivity.java | 30 ++++++++----------- .../fdroid/views/main/MainActivity.java | 8 ++--- 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/app/src/basic/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/app/src/basic/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index 4e86467a5..1dcfaf9bd 100644 --- a/app/src/basic/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/app/src/basic/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -19,10 +19,15 @@ package org.fdroid.fdroid.views.swap; +import android.content.Context; +import android.net.Uri; + /** * Dummy version for basic app flavor. */ public class SwapWorkflowActivity { public static final String EXTRA_PREVENT_FURTHER_SWAP_REQUESTS = "preventFurtherSwap"; public static final String EXTRA_CONFIRM = "EXTRA_CONFIRM"; + public static void requestSwap(Context context, Uri uri) { + }; } diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index b1db868dc..83839d650 100644 --- a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -62,6 +62,7 @@ import org.fdroid.fdroid.localrepo.peers.Peer; import org.fdroid.fdroid.net.BluetoothDownloader; import org.fdroid.fdroid.net.HttpDownloader; import org.fdroid.fdroid.net.WifiStateChangeService; +import org.fdroid.fdroid.views.main.MainActivity; import org.fdroid.fdroid.views.swap.device.camera.CameraCharacteristicsChecker; import java.util.Date; @@ -72,6 +73,8 @@ import java.util.Set; import java.util.Timer; import java.util.TimerTask; +import static org.fdroid.fdroid.views.main.MainActivity.ACTION_REQUEST_SWAP; + /** * This activity will do its best to show the most relevant screen about swapping to the user. * The problem comes when there are two competing goals - 1) Show the user a list of apps from another @@ -89,13 +92,6 @@ public class SwapWorkflowActivity extends AppCompatActivity { * among each other offering swaps. */ public static final String EXTRA_PREVENT_FURTHER_SWAP_REQUESTS = "preventFurtherSwap"; - public static final String EXTRA_CONFIRM = "EXTRA_CONFIRM"; - - /** - * Ensure that we don't try to handle specific intents more than once in onResume() - * (e.g. the "Do you want to swap back with ..." intent). - */ - public static final String EXTRA_SWAP_INTENT_HANDLED = "swapIntentHandled"; private ViewGroup container; @@ -113,10 +109,11 @@ public class SwapWorkflowActivity extends AppCompatActivity { private WifiManager wifiManager; public static void requestSwap(Context context, String repo) { - Uri repoUri = Uri.parse(repo); - Intent intent = new Intent(context, SwapWorkflowActivity.class); - intent.setData(repoUri); - intent.putExtra(EXTRA_CONFIRM, true); + requestSwap(context, Uri.parse(repo)); + } + + public static void requestSwap(Context context, Uri uri) { + Intent intent = new Intent(MainActivity.ACTION_REQUEST_SWAP, uri, context, SwapWorkflowActivity.class); intent.putExtra(EXTRA_PREVENT_FURTHER_SWAP_REQUESTS, true); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); @@ -358,19 +355,16 @@ public class SwapWorkflowActivity extends AppCompatActivity { */ private void checkIncomingIntent() { Intent intent = getIntent(); + if (!ACTION_REQUEST_SWAP.equals(intent.getAction())) { + return; + } Uri uri = intent.getData(); if (uri != null && !HttpDownloader.isSwapUrl(uri) && !BluetoothDownloader.isBluetoothUri(uri)) { String msg = getString(R.string.swap_toast_invalid_url, uri); Toast.makeText(this, msg, Toast.LENGTH_LONG).show(); return; } - - if (intent.getBooleanExtra(EXTRA_CONFIRM, false) && !intent.getBooleanExtra(EXTRA_SWAP_INTENT_HANDLED, false)) { - // Storing config in this variable will ensure that when showRelevantView() is next - // run, it will show the connect swap view (if the service is available). - intent.putExtra(EXTRA_SWAP_INTENT_HANDLED, true); - confirmSwapConfig = new NewRepoConfig(this, intent); - } + confirmSwapConfig = new NewRepoConfig(this, intent); } public void promptToSelectWifiNetwork() { diff --git a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java index 5e9daf06b..c04116e63 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java @@ -92,11 +92,10 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB private static final String ADD_REPO_INTENT_HANDLED = "addRepoIntentHandled"; private static final String ACTION_ADD_REPO = "org.fdroid.fdroid.MainActivity.ACTION_ADD_REPO"; + public static final String ACTION_REQUEST_SWAP = "requestSwap"; private static final String STATE_SELECTED_MENU_ID = "selectedMenuId"; - private static final int REQUEST_SWAP = 3; - private RecyclerView pager; private MainViewAdapter adapter; private BottomNavigationBar bottomNavigation; @@ -390,10 +389,7 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB NewRepoConfig parser = new NewRepoConfig(this, intent); if (parser.isValidRepo()) { if (parser.isFromSwap()) { - Intent confirmIntent = new Intent(this, SwapWorkflowActivity.class); - confirmIntent.putExtra(SwapWorkflowActivity.EXTRA_CONFIRM, true); - confirmIntent.setData(intent.getData()); - startActivityForResult(confirmIntent, REQUEST_SWAP); + SwapWorkflowActivity.requestSwap(this, intent.getData()); } else { Intent clean = new Intent(ACTION_ADD_REPO, intent.getData(), this, ManageReposActivity.class); if (intent.hasExtra(ManageReposActivity.EXTRA_FINISH_AFTER_ADDING_REPO)) { From 1e1ea03bc34506d4b6784d70ed8b4cc43377df61 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 15 May 2019 13:34:47 +0200 Subject: [PATCH 06/41] move ConfirmReceiveView to pure XML SwapView with logic in Presenter --- .../fdroid/views/swap/ConfirmReceiveView.java | 54 ------------------- .../views/swap/SwapWorkflowActivity.java | 42 +++++++++++---- .../full/res/layout/swap_confirm_receive.xml | 8 +-- 3 files changed, 36 insertions(+), 68 deletions(-) delete mode 100644 app/src/full/java/org/fdroid/fdroid/views/swap/ConfirmReceiveView.java diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/ConfirmReceiveView.java b/app/src/full/java/org/fdroid/fdroid/views/swap/ConfirmReceiveView.java deleted file mode 100644 index 7cce11196..000000000 --- a/app/src/full/java/org/fdroid/fdroid/views/swap/ConfirmReceiveView.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.fdroid.fdroid.views.swap; - -import android.annotation.TargetApi; -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.data.NewRepoConfig; -import org.fdroid.fdroid.localrepo.SwapView; - -public class ConfirmReceiveView extends SwapView { - - private NewRepoConfig config; - - public ConfirmReceiveView(Context context) { - super(context); - } - - public ConfirmReceiveView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public ConfirmReceiveView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @TargetApi(21) - public ConfirmReceiveView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - findViewById(R.id.no_button).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - getActivity().denySwap(); - } - }); - - findViewById(R.id.yes_button).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - getActivity().swapWith(config); - } - }); - } - - public void setup(NewRepoConfig config) { - this.config = config; - } -} diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index 83839d650..9b706905a 100644 --- a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -435,11 +435,9 @@ public class SwapWorkflowActivity extends AppCompatActivity { return; } - // This is separate from the switch statement below, because it is usually populated - // during onResume, when there is a high probability of not having a swap service - // available. Thus, we were unable to set the state of the swap service appropriately. if (confirmSwapConfig != null) { - showConfirmSwap(confirmSwapConfig); + inflateSwapView(R.layout.swap_confirm_receive); + setUpConfirmReceive(); confirmSwapConfig = null; return; } @@ -528,12 +526,6 @@ public class SwapWorkflowActivity extends AppCompatActivity { inflateSwapView(R.layout.swap_start_swap); } - private void showConfirmSwap(@NonNull NewRepoConfig config) { - ((ConfirmReceiveView) inflateSwapView(R.layout.swap_confirm_receive)).setup(config); - TextView descriptionTextView = (TextView) findViewById(R.id.text_description); - descriptionTextView.setText(getResources().getString(R.string.swap_confirm_connect, config.getHost())); - } - public void startQrWorkflow() { if (!getService().isEnabled()) { new AlertDialog.Builder(this) @@ -993,4 +985,34 @@ public class SwapWorkflowActivity extends AppCompatActivity { }); } } + + private void setUpConfirmReceive() { + TextView descriptionTextView = findViewById(R.id.text_description); + if (descriptionTextView != null) { + descriptionTextView.setText(getString(R.string.swap_confirm_connect, confirmSwapConfig.getHost())); + } + + Button confirmReceiveYes = container.findViewById(R.id.confirm_receive_yes); + if (confirmReceiveYes != null) { + findViewById(R.id.confirm_receive_yes).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + denySwap(); + } + }); + } + + Button confirmReceiveNo = container.findViewById(R.id.confirm_receive_no); + if (confirmReceiveNo != null) { + findViewById(R.id.confirm_receive_no).setOnClickListener(new View.OnClickListener() { + + private final NewRepoConfig config = confirmSwapConfig; + + @Override + public void onClick(View v) { + swapWith(config); + } + }); + } + } } diff --git a/app/src/full/res/layout/swap_confirm_receive.xml b/app/src/full/res/layout/swap_confirm_receive.xml index 93cb17a52..bb92ad9b1 100644 --- a/app/src/full/res/layout/swap_confirm_receive.xml +++ b/app/src/full/res/layout/swap_confirm_receive.xml @@ -1,6 +1,6 @@ -