diff --git a/app/build.gradle b/app/build.gradle index 35eacfea8..c6699eda9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -40,7 +40,7 @@ dependencies { compile "info.guardianproject.panic:panic:0.5" compile 'commons-io:commons-io:2.5' compile 'commons-net:commons-net:3.5' - compile 'org.openhab.jmdns:jmdns:3.4.2' + compile 'org.jmdns:jmdns:3.5.3' compile 'ch.acra:acra:4.9.1' compile 'io.reactivex:rxjava:1.1.0' compile 'io.reactivex:rxandroid:0.23.0' @@ -137,7 +137,8 @@ if (!hasProperty('sourceDeps')) { 'info.guardianproject.panic:panic:a7ed9439826db2e9901649892cf9afbe76f00991b768d8f4c26332d7c9406cb2', 'io.reactivex:rxandroid:35c1a90f8c1f499db3c1f3d608e1f191ac8afddb10c02dd91ef04c03a0a4bcda', 'io.reactivex:rxjava:2c162afd78eba217cdfee78b60e85d3bfb667db61e12bc95e3cf2ddc5beeadf6', - 'org.openhab.jmdns:jmdns:7a4b34b5606bbd2aff7fdfe629edcb0416fccd367fb59a099f210b9aba4f0bce', + 'org.jmdns:jmdns:24e7e3a50a579136400e8c9b0750399eb3c7558918bdf52c0ffa5e0fa5aad503', + 'org.slf4j:slf4j-api:e56288031f5e60652c06e7bb6e9fa410a61231ab54890f7b708fc6adc4107c5b', ] } @@ -181,7 +182,6 @@ def preDexEnabled = "true".equals(System.getProperty("pre-dex", "true")) android { compileSdkVersion 24 buildToolsVersion '25.0.3' - useLibrary 'org.apache.http.legacy' buildTypes { // use proguard on debug too since we have unknowingly broken diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 517c234ec..06752191a 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -7,6 +7,9 @@ -dontwarn com.android.support.test.** -dontwarn javax.naming.** +-dontwarn org.slf4j.** +-dontnote org.apache.http.** +-dontnote android.net.http.** -dontnote android.support.** -dontnote **ILicensingService diff --git a/app/src/main/java/javax/jmdns/impl/FDroidServiceInfo.java b/app/src/main/java/javax/jmdns/impl/FDroidServiceInfo.java index 2851cb2d1..2fed8a798 100644 --- a/app/src/main/java/javax/jmdns/impl/FDroidServiceInfo.java +++ b/app/src/main/java/javax/jmdns/impl/FDroidServiceInfo.java @@ -4,12 +4,12 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import javax.jmdns.ServiceInfo; +import javax.jmdns.impl.util.ByteWrangler; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.UnknownHostException; -import javax.jmdns.ServiceInfo; - /** * The ServiceInfo class needs to be serialized in order to be sent as an Android broadcast. * In order to make it Parcelable (or Serializable for that matter), there are some package-scope @@ -32,7 +32,7 @@ public class FDroidServiceInfo extends ServiceInfoImpl implements Parcelable { if (data == null || data.length == 0) { return null; } - String fingerprint = this.readUTF(data, 0, data.length); + String fingerprint = ByteWrangler.readUTF(data, 0, data.length); if (TextUtils.isEmpty(fingerprint)) { return null; } @@ -51,14 +51,14 @@ public class FDroidServiceInfo extends ServiceInfoImpl implements Parcelable { public FDroidServiceInfo(Parcel in) { super( - in.readString(), - in.readString(), - in.readString(), - in.readInt(), - in.readInt(), - in.readInt(), - in.readByte() != 0, - readBytes(in) + in.readString(), + in.readString(), + in.readString(), + in.readInt(), + in.readInt(), + in.readInt(), + in.readByte() != 0, + readBytes(in) ); int addressCount = in.readInt(); diff --git a/app/src/main/java/org/fdroid/fdroid/UpdateService.java b/app/src/main/java/org/fdroid/fdroid/UpdateService.java index ab0dc40dc..20360a5f8 100644 --- a/app/src/main/java/org/fdroid/fdroid/UpdateService.java +++ b/app/src/main/java/org/fdroid/fdroid/UpdateService.java @@ -47,6 +47,7 @@ import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.Schema; import org.fdroid.fdroid.installer.InstallManagerService; +import org.fdroid.fdroid.net.BluetoothDownloader; import org.fdroid.fdroid.net.ConnectivityMonitorService; import org.fdroid.fdroid.views.main.MainActivity; @@ -332,7 +333,7 @@ public class UpdateService extends IntentService { boolean forcedUpdate = false; String address = null; if (intent != null) { - address = intent.getStringExtra(EXTRA_ADDRESS); + address = intent.getStringExtra(EXTRA_ADDRESS); // TODO switch to Intent.setData() manualUpdate = intent.getBooleanExtra(EXTRA_MANUAL_UPDATE, false); forcedUpdate = intent.getBooleanExtra(EXTRA_FORCED_UPDATE, false); } @@ -340,7 +341,9 @@ public class UpdateService extends IntentService { try { // See if it's time to actually do anything yet... int netState = ConnectivityMonitorService.getNetworkState(this); - if (netState == ConnectivityMonitorService.FLAG_NET_UNAVAILABLE) { + if (address != null && address.startsWith(BluetoothDownloader.SCHEME)) { + Utils.debugLog(TAG, "skipping internet check, this is bluetooth"); + } else if (netState == ConnectivityMonitorService.FLAG_NET_UNAVAILABLE) { Utils.debugLog(TAG, "No internet, cannot update"); if (manualUpdate) { sendNoInternetToast(); diff --git a/app/src/main/java/org/fdroid/fdroid/localrepo/SwapService.java b/app/src/main/java/org/fdroid/fdroid/localrepo/SwapService.java index 6f8060b9d..b7e509179 100644 --- a/app/src/main/java/org/fdroid/fdroid/localrepo/SwapService.java +++ b/app/src/main/java/org/fdroid/fdroid/localrepo/SwapService.java @@ -1,8 +1,10 @@ package org.fdroid.fdroid.localrepo; +import android.annotation.SuppressLint; import android.app.Notification; import android.app.PendingIntent; import android.app.Service; +import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.ContentValues; import android.content.Context; @@ -10,7 +12,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.net.Uri; -import android.net.http.AndroidHttpClient; +import android.net.wifi.WifiManager; import android.os.AsyncTask; import android.os.IBinder; import android.support.annotation.IntDef; @@ -20,11 +22,6 @@ import android.support.v4.app.NotificationCompat; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; import android.util.Log; -import org.apache.http.HttpHost; -import org.apache.http.NameValuePair; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.message.BasicNameValuePair; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.R; @@ -47,13 +44,14 @@ import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; import java.io.IOException; -import java.io.UnsupportedEncodingException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; +import java.net.HttpURLConnection; +import java.net.URL; import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Set; import java.util.Timer; import java.util.TimerTask; @@ -67,10 +65,13 @@ import java.util.concurrent.ConcurrentHashMap; public class SwapService extends Service { private static final String TAG = "SwapService"; + private static final String SHARED_PREFERENCES = "swap-state"; private static final String KEY_APPS_TO_SWAP = "appsToSwap"; private static final String KEY_BLUETOOTH_ENABLED = "bluetoothEnabled"; private static final String KEY_WIFI_ENABLED = "wifiEnabled"; + private static final String KEY_BLUETOOTH_ENABLED_BEFORE_SWAP = "bluetoothEnabledBeforeSwap"; + private static final String KEY_WIFI_ENABLED_BEFORE_SWAP = "wifiEnabledBeforeSwap"; @NonNull private final Set appsToSwap = new HashSet<>(); @@ -80,6 +81,10 @@ public class SwapService extends Service { */ private static final ConcurrentHashMap INSTALLED_APPS = new ConcurrentHashMap<>(); + private static SharedPreferences swapPreferences; + private static BluetoothAdapter bluetoothAdapter; + private static WifiManager wifiManager; + public static void stop(Context context) { Intent intent = new Intent(context, SwapService.class); context.stopService(intent); @@ -93,16 +98,6 @@ public class SwapService extends Service { INSTALLED_APPS.put(packageName, app); } - /** - * Where relevant, the state of the swap process will be saved to disk using preferences. - * Note that this is not always useful, for example saving the "current wifi network" is - * bound to cause trouble when the user opens the swap process again and is connected to - * a different network. - */ - private SharedPreferences persistence() { - return getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); - } - // ========================================================== // Search for peers to swap // ========================================================== @@ -208,55 +203,37 @@ public class SwapService extends Service { UpdateService.updateRepoNow(this, peer.getRepoAddress()); } + @SuppressLint("StaticFieldLeak") private void askServerToSwapWithUs(final Repo repo) { - askServerToSwapWithUs(repo.address); - } - - private void askServerToSwapWithUs(final String address) { new AsyncTask() { @Override protected Void doInBackground(Void... args) { - Uri repoUri = Uri.parse(address); String swapBackUri = Utils.getLocalRepoUri(FDroidApp.repo).toString(); - - AndroidHttpClient client = AndroidHttpClient.newInstance("F-Droid", SwapService.this); - HttpPost request = new HttpPost("/request-swap"); - HttpHost host = new HttpHost(repoUri.getHost(), repoUri.getPort(), repoUri.getScheme()); - + HttpURLConnection conn = null; try { - Utils.debugLog(TAG, "Asking server at " + address + " to swap with us in return (by POSTing to \"/request-swap\" with repo \"" + swapBackUri + "\")..."); - populatePostParams(swapBackUri, request); - client.execute(host, request); + URL url = new URL(repo.address.replace("/fdroid/repo", "/request-swap")); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setDoInput(true); + conn.setDoOutput(true); + + OutputStream outputStream = conn.getOutputStream(); + OutputStreamWriter writer = new OutputStreamWriter(outputStream); + writer.write("repo=" + swapBackUri); + writer.flush(); + writer.close(); + outputStream.close(); + + int responseCode = conn.getResponseCode(); + Utils.debugLog(TAG, "Asking server at " + repo.address + " to swap with us in return (by " + + "POSTing to \"/request-swap\" with repo \"" + swapBackUri + "\"): " + responseCode); } catch (IOException e) { - notifyOfErrorOnUiThread(); Log.e(TAG, "Error while asking server to swap with us", e); } finally { - client.close(); + conn.disconnect(); } return null; } - - private void populatePostParams(String swapBackUri, HttpPost request) throws UnsupportedEncodingException { - List params = new ArrayList<>(); - params.add(new BasicNameValuePair("repo", swapBackUri)); - UrlEncodedFormEntity encodedParams = new UrlEncodedFormEntity(params); - request.setEntity(encodedParams); - } - - private void notifyOfErrorOnUiThread() { - // TODO: Broadcast error message so that whoever wants to can display a relevant - // message in the UI. This service doesn't understand the concept of UI. - /*runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText( - SwapService.this, - R.string.swap_reciprocate_failed, - Toast.LENGTH_LONG - ).show(); - } - });*/ - } }.execute(); } @@ -333,7 +310,7 @@ public class SwapService extends Service { // ========================================== private void persistAppsToSwap() { - persistence().edit().putString(KEY_APPS_TO_SWAP, serializePackages(appsToSwap)).apply(); + swapPreferences.edit().putString(KEY_APPS_TO_SWAP, serializePackages(appsToSwap)).apply(); } /** @@ -389,30 +366,36 @@ public class SwapService extends Service { persistAppsToSwap(); } - // ============================================================= - // Remember which swap technologies a user used in the past - // ============================================================= - - private final BroadcastReceiver receiveSwapStatusChanged = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - Utils.debugLog(TAG, "Remembering that Bluetooth swap " + (bluetoothSwap.isConnected() ? "IS" : "is NOT") + - " connected and WiFi swap " + (wifiSwap.isConnected() ? "IS" : "is NOT") + " connected."); - persistence().edit() - .putBoolean(KEY_BLUETOOTH_ENABLED, bluetoothSwap.isConnected()) - .putBoolean(KEY_WIFI_ENABLED, wifiSwap.isConnected()) - .apply(); - } - }; - - /* - private boolean wasBluetoothEnabled() { - return persistence().getBoolean(KEY_BLUETOOTH_ENABLED, false); + public static boolean getBluetoothVisibleUserPreference() { + return swapPreferences.getBoolean(SwapService.KEY_BLUETOOTH_ENABLED, false); } - */ - private boolean wasWifiEnabled() { - return persistence().getBoolean(KEY_WIFI_ENABLED, false); + public static void putBluetoothVisibleUserPreference(boolean visible) { + swapPreferences.edit().putBoolean(SwapService.KEY_BLUETOOTH_ENABLED, visible).apply(); + } + + public static boolean getWifiVisibleUserPreference() { + return swapPreferences.getBoolean(SwapService.KEY_WIFI_ENABLED, false); + } + + public static void putWifiVisibleUserPreference(boolean visible) { + swapPreferences.edit().putBoolean(SwapService.KEY_WIFI_ENABLED, visible).apply(); + } + + public static boolean wasBluetoothEnabledBeforeSwap() { + return swapPreferences.getBoolean(SwapService.KEY_BLUETOOTH_ENABLED_BEFORE_SWAP, false); + } + + public static void putBluetoothEnabledBeforeSwap(boolean visible) { + swapPreferences.edit().putBoolean(SwapService.KEY_BLUETOOTH_ENABLED_BEFORE_SWAP, visible).apply(); + } + + public static boolean wasWifiEnabledBeforeSwap() { + return swapPreferences.getBoolean(SwapService.KEY_WIFI_ENABLED_BEFORE_SWAP, false); + } + + public static void putWifiEnabledBeforeSwap(boolean visible) { + swapPreferences.edit().putBoolean(SwapService.KEY_WIFI_ENABLED_BEFORE_SWAP, visible).apply(); } /** @@ -452,9 +435,6 @@ public class SwapService extends Service { return wifiSwap.isConnected() && wifiSwap.getBonjour().isConnected(); } - public static final String ACTION_PEER_FOUND = "org.fdroid.fdroid.SwapManager.ACTION_PEER_FOUND"; - public static final String EXTRA_PEER = "EXTRA_PEER"; - // =============================================================== // Old SwapService stuff being merged into that. // =============================================================== @@ -503,32 +483,39 @@ public class SwapService extends Service { CacheSwapAppsService.startCaching(this); - SharedPreferences preferences = getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); + swapPreferences = getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); - appsToSwap.addAll(deserializePackages(preferences.getString(KEY_APPS_TO_SWAP, ""))); + bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (bluetoothAdapter != null) { + SwapService.putBluetoothEnabledBeforeSwap(bluetoothAdapter.isEnabled()); + } + + wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); + if (wifiManager != null) { + SwapService.putWifiEnabledBeforeSwap(wifiManager.isWifiEnabled()); + } + + appsToSwap.addAll(deserializePackages(swapPreferences.getString(KEY_APPS_TO_SWAP, ""))); bluetoothSwap = BluetoothSwap.create(this); - wifiSwap = new WifiSwap(this); + wifiSwap = new WifiSwap(this, wifiManager); Preferences.get().registerLocalRepoHttpsListeners(httpsEnabledListener); - LocalBroadcastManager.getInstance(this).registerReceiver(onWifiChange, new IntentFilter(WifiStateChangeService.BROADCAST)); + LocalBroadcastManager.getInstance(this).registerReceiver(onWifiChange, + new IntentFilter(WifiStateChangeService.BROADCAST)); - IntentFilter filter = new IntentFilter(BLUETOOTH_STATE_CHANGE); - filter.addAction(WIFI_STATE_CHANGE); - LocalBroadcastManager.getInstance(this).registerReceiver(receiveSwapStatusChanged, filter); - - /* - if (wasBluetoothEnabled()) { + if (getBluetoothVisibleUserPreference()) { Utils.debugLog(TAG, "Previously the user enabled Bluetooth swap, so enabling again automatically."); - bluetoothSwap.startInBackground(); - } - */ - - if (wasWifiEnabled()) { - Utils.debugLog(TAG, "Previously the user enabled WiFi swap, so enabling again automatically."); - wifiSwap.startInBackground(); + bluetoothSwap.startInBackground(); // TODO replace with Intent to SwapService } else { - Utils.debugLog(TAG, "WiFi was NOT enabled last time user swapped, so starting with WiFi not visible."); + Utils.debugLog(TAG, "Bluetooth was NOT enabled last time user swapped, starting not visible."); + } + + if (getWifiVisibleUserPreference()) { + Utils.debugLog(TAG, "Previously the user enabled WiFi swap, so enabling again automatically."); + wifiSwap.startInBackground(); // TODO replace with Intent to SwapService + } else { + Utils.debugLog(TAG, "WiFi was NOT enabled last time user swapped, starting not visible."); } } @@ -549,7 +536,14 @@ public class SwapService extends Service { Utils.debugLog(TAG, "Destroying service, will disable swapping if required, and unregister listeners."); Preferences.get().unregisterLocalRepoHttpsListeners(httpsEnabledListener); LocalBroadcastManager.getInstance(this).unregisterReceiver(onWifiChange); - LocalBroadcastManager.getInstance(this).unregisterReceiver(receiveSwapStatusChanged); + + if (!SwapService.wasBluetoothEnabledBeforeSwap()) { + bluetoothAdapter.disable(); + } + + if (!SwapService.wasWifiEnabledBeforeSwap()) { + wifiManager.setWifiEnabled(false); + } //TODO getBluetoothSwap().stopInBackground(); getWifiSwap().stopInBackground(); diff --git a/app/src/main/java/org/fdroid/fdroid/localrepo/type/BluetoothSwap.java b/app/src/main/java/org/fdroid/fdroid/localrepo/type/BluetoothSwap.java index b81a3fb6e..7ab9735b1 100644 --- a/app/src/main/java/org/fdroid/fdroid/localrepo/type/BluetoothSwap.java +++ b/app/src/main/java/org/fdroid/fdroid/localrepo/type/BluetoothSwap.java @@ -8,7 +8,6 @@ import android.content.IntentFilter; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; - import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.localrepo.SwapService; import org.fdroid.fdroid.net.bluetooth.BluetoothServer; @@ -59,8 +58,8 @@ public final class BluetoothSwap extends SwapType { @Override public synchronized void start() { - if (isConnected()) { + Utils.debugLog(TAG, "already running, quitting start()"); return; } @@ -171,10 +170,12 @@ public final class BluetoothSwap extends SwapType { } @Override - public void start() { } + public void start() { + } @Override - public void stop() { } + public void stop() { + } @Override protected String getBroadcastAction() { diff --git a/app/src/main/java/org/fdroid/fdroid/localrepo/type/WifiSwap.java b/app/src/main/java/org/fdroid/fdroid/localrepo/type/WifiSwap.java index 152877aec..f76dbe042 100644 --- a/app/src/main/java/org/fdroid/fdroid/localrepo/type/WifiSwap.java +++ b/app/src/main/java/org/fdroid/fdroid/localrepo/type/WifiSwap.java @@ -2,6 +2,7 @@ package org.fdroid.fdroid.localrepo.type; import android.annotation.SuppressLint; import android.content.Context; +import android.net.wifi.WifiManager; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -31,10 +32,12 @@ public class WifiSwap extends SwapType { private Handler webServerThreadHandler; private LocalHTTPD localHttpd; private final BonjourBroadcast bonjourBroadcast; + private final WifiManager wifiManager; - public WifiSwap(Context context) { + public WifiSwap(Context context, WifiManager wifiManager) { super(context); bonjourBroadcast = new BonjourBroadcast(context); + this.wifiManager = wifiManager; } protected String getBroadcastAction() { @@ -47,6 +50,8 @@ public class WifiSwap extends SwapType { @Override public void start() { + wifiManager.setWifiEnabled(true); + Utils.debugLog(TAG, "Preparing swap webserver."); sendBroadcast(SwapService.EXTRA_STARTING); diff --git a/app/src/main/java/org/fdroid/fdroid/net/BluetoothDownloader.java b/app/src/main/java/org/fdroid/fdroid/net/BluetoothDownloader.java index 85d4f4d84..225be7434 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/BluetoothDownloader.java +++ b/app/src/main/java/org/fdroid/fdroid/net/BluetoothDownloader.java @@ -14,15 +14,27 @@ import org.fdroid.fdroid.net.bluetooth.httpish.Response; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.regex.Pattern; +/** + * Download from a Bluetooth swap repo. Example URI: + * {@code bluetooth://84-CF-BF-8B-3E-34/fdroid/repo} + */ public class BluetoothDownloader extends Downloader { private static final String TAG = "BluetoothDownloader"; + public static final String SCHEME = "bluetooth"; + private final BluetoothConnection connection; private FileDetails fileDetails; private final String sourcePath; + public static boolean isBluetoothUri(Uri uri) { + return SCHEME.equals(uri.getScheme()) + && Pattern.matches("([0-9A-F]{2}-)+[0-9A-F]{2}", uri.getHost()); + } + public BluetoothDownloader(Uri uri, File destFile) throws IOException { super(uri, destFile); String macAddress = uri.getHost().replace("-", ":"); diff --git a/app/src/main/java/org/fdroid/fdroid/net/HttpDownloader.java b/app/src/main/java/org/fdroid/fdroid/net/HttpDownloader.java index 880d48fa5..619f48c00 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/HttpDownloader.java +++ b/app/src/main/java/org/fdroid/fdroid/net/HttpDownloader.java @@ -122,16 +122,23 @@ public class HttpDownloader extends Downloader { cacheTag = connection.getHeaderField(HEADER_FIELD_ETAG); } - private boolean isSwapUrl() { - String host = sourceUrl.getHost(); - return sourceUrl.getPort() > 1023 // only root can use <= 1023, so never a swap repo + public static boolean isSwapUrl(Uri uri) { + return isSwapUrl(uri.getHost(), uri.getPort()); + } + + public static boolean isSwapUrl(URL url) { + return isSwapUrl(url.getHost(), url.getPort()); + } + + public static boolean isSwapUrl(String host, int port) { + return port > 1023 // only root can use <= 1023, so never a swap repo && host.matches("[0-9.]+") // host must be an IP address && FDroidApp.subnetInfo.isInRange(host); // on the same subnet as we are } private HttpURLConnection getConnection() throws SocketTimeoutException, IOException { HttpURLConnection connection; - if (isSwapUrl()) { + if (isSwapUrl(sourceUrl)) { // swap never works with a proxy, its unrouted IP on the same subnet connection = (HttpURLConnection) sourceUrl.openConnection(); } else { diff --git a/app/src/main/java/org/fdroid/fdroid/net/bluetooth/BluetoothServer.java b/app/src/main/java/org/fdroid/fdroid/net/bluetooth/BluetoothServer.java index 7ba308a3f..86d8a987d 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/bluetooth/BluetoothServer.java +++ b/app/src/main/java/org/fdroid/fdroid/net/bluetooth/BluetoothServer.java @@ -66,7 +66,7 @@ public class BluetoothServer extends Thread { public void run() { isRunning = true; - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); try { serverSocket = adapter.listenUsingInsecureRfcommWithServiceRecord("FDroid App Swap", BluetoothConstants.fdroidUuid()); @@ -83,6 +83,11 @@ public class BluetoothServer extends Thread { break; } + if (!adapter.isEnabled()) { + Utils.debugLog(TAG, "User disabled Bluetooth from outside, stopping."); + break; + } + try { BluetoothSocket clientSocket = serverSocket.accept(); if (clientSocket != null) { diff --git a/app/src/main/java/org/fdroid/fdroid/views/swap/SendFDroidView.java b/app/src/main/java/org/fdroid/fdroid/views/swap/SendFDroidView.java new file mode 100644 index 000000000..60bf9fc02 --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/views/swap/SendFDroidView.java @@ -0,0 +1,149 @@ +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.annotation.ColorRes; +import android.support.annotation.NonNull; +import android.support.v4.content.LocalBroadcastManager; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.ScrollView; +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.SwapService; +import org.fdroid.fdroid.net.WifiStateChangeService; +import org.fdroid.fdroid.views.swap.device.camera.CameraCharacteristicsChecker; + +public class SendFDroidView extends ScrollView implements SwapWorkflowActivity.InnerView { + + 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); + } + + private SwapWorkflowActivity getActivity() { + return (SwapWorkflowActivity) getContext(); + } + + @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() { + @Override + public void onClick(View v) { + getActivity().showIntro(); + 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); + } + + @Override + public boolean buildMenu(Menu menu, @NonNull MenuInflater inflater) { + return false; + } + + @Override + public int getStep() { + return SwapService.STEP_INTRO; + } + + @Override + public int getPreviousStep() { + return SwapService.STEP_INTRO; + } + + @ColorRes + public int getToolbarColour() { + return R.color.swap_blue; + } + + @Override + public String getToolbarTitle() { + return getResources().getString(R.string.swap_send_fdroid); + } + + @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/main/java/org/fdroid/fdroid/views/swap/StartSwapView.java b/app/src/main/java/org/fdroid/fdroid/views/swap/StartSwapView.java index 72270d387..1891468cd 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/swap/StartSwapView.java +++ b/app/src/main/java/org/fdroid/fdroid/views/swap/StartSwapView.java @@ -324,6 +324,7 @@ public class StartSwapView extends RelativeLayout implements SwapWorkflowActivit viewBluetoothId.setVisibility(View.GONE); Utils.debugLog(TAG, "Received onCheckChanged(false) for Bluetooth swap, Bluetooth swap disabled successfully."); } + SwapService.putBluetoothVisibleUserPreference(isChecked); } }; @@ -344,7 +345,8 @@ public class StartSwapView extends RelativeLayout implements SwapWorkflowActivit // and the Bonjour service at the same time. Technically swap will work fine without // Bonjour, and that is more of a convenience. Thus, we should show feedback once wifi // is ready, even if Bonjour is not yet. - LocalBroadcastManager.getInstance(getContext()).registerReceiver(onWifiSwapStateChanged, new IntentFilter(SwapService.WIFI_STATE_CHANGE)); + LocalBroadcastManager.getInstance(getContext()).registerReceiver(onWifiSwapStateChanged, + new IntentFilter(SwapService.WIFI_STATE_CHANGE)); viewWifiNetwork.setOnClickListener(new OnClickListener() { @Override @@ -426,6 +428,7 @@ public class StartSwapView extends RelativeLayout implements SwapWorkflowActivit Utils.debugLog(TAG, "Received onCheckChanged(false) for WiFi swap, disabling WiFi swap in background thread."); getManager().getWifiSwap().stopInBackground(); } + SwapService.putWifiVisibleUserPreference(isChecked); uiUpdateWifiNetwork(); } }; diff --git a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index 4bbfbd4f1..6098a58cd 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -1,5 +1,6 @@ package org.fdroid.fdroid.views.swap; +import android.annotation.TargetApi; import android.app.Activity; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; @@ -12,8 +13,10 @@ 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; +import android.provider.Settings; import android.support.annotation.ColorRes; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; @@ -29,10 +32,9 @@ import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; - +import cc.mvdan.accesspoint.WifiApControl; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; - import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.NfcHelper; @@ -47,6 +49,8 @@ import org.fdroid.fdroid.installer.Installer; import org.fdroid.fdroid.localrepo.LocalRepoManager; import org.fdroid.fdroid.localrepo.SwapService; 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; @@ -57,8 +61,6 @@ import java.util.Set; import java.util.Timer; import java.util.TimerTask; -import cc.mvdan.accesspoint.WifiApControl; - /** * 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 @@ -111,6 +113,7 @@ public class SwapWorkflowActivity extends AppCompatActivity { private static final int REQUEST_BLUETOOTH_ENABLE_FOR_SWAP = 2; private static final int REQUEST_BLUETOOTH_DISCOVERABLE = 3; private static final int REQUEST_BLUETOOTH_ENABLE_FOR_SEND = 4; + private static final int REQUEST_WRITE_SETTINGS_PERMISSION = 5; private Toolbar toolbar; private InnerView currentView; @@ -118,6 +121,7 @@ public class SwapWorkflowActivity extends AppCompatActivity { private PrepareSwapRepo updateSwappableAppsTask; private NewRepoConfig confirmSwapConfig; private LocalBroadcastManager localBroadcastManager; + private WifiManager wifiManager; @NonNull private final ServiceConnection serviceConnection = new ServiceConnection() { @@ -183,6 +187,7 @@ public class SwapWorkflowActivity extends AppCompatActivity { container = (ViewGroup) findViewById(R.id.fragment_container); localBroadcastManager = LocalBroadcastManager.getInstance(this); + wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); new SwapDebug().logStatus(); } @@ -197,7 +202,7 @@ public class SwapWorkflowActivity extends AppCompatActivity { public boolean onPrepareOptionsMenu(Menu menu) { menu.clear(); boolean parent = super.onPrepareOptionsMenu(menu); - boolean inner = currentView != null && currentView.buildMenu(menu, getMenuInflater()); + boolean inner = currentView != null && currentView.buildMenu(menu, getMenuInflater()); return parent || inner; } @@ -209,8 +214,20 @@ public class SwapWorkflowActivity extends AppCompatActivity { showRelevantView(); } + /** + * 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 + * Bluetooth address. + */ private void checkIncomingIntent() { Intent intent = getIntent(); + 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). @@ -239,32 +256,40 @@ public class SwapWorkflowActivity extends AppCompatActivity { public void onClick(DialogInterface dialog, int which) { // Do nothing } - } - ).setPositiveButton(R.string.wifi, new DialogInterface.OnClickListener() { + }) + .setPositiveButton(R.string.wifi, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - startActivity(new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK)); + SwapService.putWifiEnabledBeforeSwap(wifiManager.isWifiEnabled()); + wifiManager.setWifiEnabled(true); + Intent intent = new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); } - } - ).setNegativeButton(R.string.wifi_ap, new DialogInterface.OnClickListener() { + }) + .setNegativeButton(R.string.wifi_ap, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - promptToSetupWifiAP(); + if (Build.VERSION.SDK_INT >= 26) { + showTetheringSettings(); + } else if (Build.VERSION.SDK_INT >= 23 && !Settings.System.canWrite(getBaseContext())) { + requestWriteSettingsPermission(); + } else { + setupWifiAP(); + } } - } - ).create().show(); + }) + .create().show(); } - private void promptToSetupWifiAP() { - WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); + private void setupWifiAP() { WifiApControl ap = WifiApControl.getInstance(this); wifiManager.setWifiEnabled(false); - if (!ap.enable()) { - Log.e(TAG, "Could not enable WiFi AP."); - // TODO: Feedback to user? + if (ap.enable()) { + Toast.makeText(this, R.string.swap_toast_hotspot_enabled, Toast.LENGTH_SHORT).show(); } else { - Utils.debugLog(TAG, "WiFi AP enabled."); - // TODO: Seems to be broken some times... + Toast.makeText(this, R.string.swap_toast_could_not_enable_hotspot, Toast.LENGTH_LONG).show(); + Log.e(TAG, "Could not enable WiFi AP."); } } @@ -408,29 +433,52 @@ public class SwapWorkflowActivity extends AppCompatActivity { inflateInnerView(R.layout.swap_select_apps); } + /** + * On {@code android-26}, only apps with privileges can access + * {@code WRITE_SETTINGS}. So this just shows the tethering settings + * for the user to do it themselves. + */ + public void showTetheringSettings() { + final Intent intent = new Intent(Intent.ACTION_MAIN, null); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + final ComponentName cn = new ComponentName("com.android.settings", + "com.android.settings.TetherSettings"); + intent.setComponent(cn); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + + @TargetApi(23) + public void requestWriteSettingsPermission() { + Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS, + Uri.parse("package:" + getPackageName())); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivityForResult(intent, REQUEST_WRITE_SETTINGS_PERMISSION); + } + public void sendFDroid() { - // If Bluetooth has not been enabled/turned on, then enabling device discoverability - // will automatically enable Bluetooth. BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null) { - if (adapter.getState() != BluetoothAdapter.STATE_ON) { - Intent discoverBt = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); - discoverBt.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120); - startActivityForResult(discoverBt, REQUEST_BLUETOOTH_ENABLE_FOR_SEND); - } else { - sendFDroidApk(); - } + if (adapter == null + || Build.VERSION.SDK_INT >= 23 // TODO make Bluetooth work with content:// URIs + || (!adapter.isEnabled() && getService().getWifiSwap().isConnected())) { + showSendFDroid(); } else { - new AlertDialog.Builder(this) - .setTitle(R.string.bluetooth_unavailable) - .setMessage(R.string.swap_cant_send_no_bluetooth) - .setNegativeButton( - R.string.cancel, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { } - } - ).create().show(); + sendFDroidBluetooth(); + } + } + + /** + * Send the F-Droid APK via Bluetooth. If Bluetooth has not been + * enabled/turned on, then enabling device discoverability will + * automatically enable Bluetooth. + */ + public void sendFDroidBluetooth() { + if (BluetoothAdapter.getDefaultAdapter().isEnabled()) { + sendFDroidApk(); + } else { + Intent discoverBt = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); + discoverBt.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120); + startActivityForResult(discoverBt, REQUEST_BLUETOOTH_ENABLE_FOR_SEND); } } @@ -483,6 +531,10 @@ public class SwapWorkflowActivity extends AppCompatActivity { inflateInnerView(R.layout.swap_wifi_qr); } + public void showSendFDroid() { + inflateInnerView(R.layout.swap_send_fdroid); + } + public void showSwapConnected() { inflateInnerView(R.layout.swap_success); } @@ -557,23 +609,28 @@ public class SwapWorkflowActivity extends AppCompatActivity { } } else if (requestCode == CONNECT_TO_SWAP && resultCode == Activity.RESULT_OK) { finish(); + } else if (requestCode == REQUEST_WRITE_SETTINGS_PERMISSION) { + if (Build.VERSION.SDK_INT >= 23 && Settings.System.canWrite(this)) { + setupWifiAP(); + } } else if (requestCode == REQUEST_BLUETOOTH_ENABLE_FOR_SWAP) { if (resultCode == RESULT_OK) { Utils.debugLog(TAG, "User enabled Bluetooth, will make sure we are discoverable."); ensureBluetoothDiscoverableThenStart(); } else { - // Didn't enable bluetooth - Utils.debugLog(TAG, "User chose not to enable Bluetooth, so doing nothing (i.e. sticking with wifi)."); + Utils.debugLog(TAG, "User chose not to enable Bluetooth, so doing nothing"); + SwapService.putBluetoothVisibleUserPreference(false); } } else if (requestCode == REQUEST_BLUETOOTH_DISCOVERABLE) { if (resultCode != RESULT_CANCELED) { Utils.debugLog(TAG, "User made Bluetooth discoverable, will proceed to start bluetooth server."); - getState().getBluetoothSwap().startInBackground(); + getState().getBluetoothSwap().startInBackground(); // TODO replace with Intent to SwapService } else { - Utils.debugLog(TAG, "User chose not to make Bluetooth discoverable, so doing nothing (i.e. sticking with wifi)."); + Utils.debugLog(TAG, "User chose not to make Bluetooth discoverable, so doing nothing"); + SwapService.putBluetoothVisibleUserPreference(false); } } else if (requestCode == REQUEST_BLUETOOTH_ENABLE_FOR_SEND) { @@ -583,12 +640,13 @@ public class SwapWorkflowActivity extends AppCompatActivity { /** * The process for setting up bluetooth is as follows: - * * Assume we have bluetooth available (otherwise the button which allowed us to start - * the bluetooth process should not have been available). - * * Ask user to enable (if not enabled yet). - * * Start bluetooth server socket. - * * Enable bluetooth discoverability, so that people can connect to our server socket. - * + *
    + *
  • Assume we have bluetooth available (otherwise the button which allowed us to start + * the bluetooth process should not have been available)
  • + *
  • Ask user to enable (if not enabled yet)
  • + *
  • Start bluetooth server socket
  • + *
  • Enable bluetooth discoverability, so that people can connect to our server socket.
  • + *
* Note that this is a little different than the usual process for bluetooth _clients_, which * involves pairing and connecting with other devices. */ @@ -629,12 +687,12 @@ public class SwapWorkflowActivity extends AppCompatActivity { throw new IllegalStateException("Can't start Bluetooth swap because service is null for some strange reason."); } - service.getBluetoothSwap().startInBackground(); + service.getBluetoothSwap().startInBackground(); // TODO replace with Intent to SwapService } class PrepareInitialSwapRepo extends PrepareSwapRepo { PrepareInitialSwapRepo() { - super(new HashSet<>(Arrays.asList(new String[] {BuildConfig.APPLICATION_ID}))); + super(new HashSet<>(Arrays.asList(new String[]{BuildConfig.APPLICATION_ID}))); } } diff --git a/app/src/main/java/org/fdroid/fdroid/views/swap/WifiQrView.java b/app/src/main/java/org/fdroid/fdroid/views/swap/WifiQrView.java index dd73267c6..8ec198c8b 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/swap/WifiQrView.java +++ b/app/src/main/java/org/fdroid/fdroid/views/swap/WifiQrView.java @@ -7,6 +7,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.graphics.LightingColorFilter; import android.net.Uri; +import android.os.Build; import android.support.annotation.ColorRes; import android.support.annotation.NonNull; import android.support.v4.content.LocalBroadcastManager; @@ -19,9 +20,6 @@ import android.widget.Button; import android.widget.ImageView; import android.widget.ScrollView; import android.widget.TextView; - -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URLEncodedUtils; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.QrGenAsyncTask; @@ -31,9 +29,8 @@ import org.fdroid.fdroid.localrepo.SwapService; import org.fdroid.fdroid.net.WifiStateChangeService; import org.fdroid.fdroid.views.swap.device.camera.CameraCharacteristicsChecker; -import java.net.URI; -import java.util.List; import java.util.Locale; +import java.util.Set; public class WifiQrView extends ScrollView implements SwapWorkflowActivity.InnerView { @@ -142,32 +139,34 @@ public class WifiQrView extends ScrollView implements SwapWorkflowActivity.Inner ipAddressView.setText(buttonLabel); Uri sharingUri = Utils.getSharingUri(FDroidApp.repo); - String qrUriString = scheme + sharingUri.getHost(); + StringBuilder qrUrlBuilder = new StringBuilder(scheme); + qrUrlBuilder.append(sharingUri.getHost()); if (sharingUri.getPort() != 80) { - qrUriString += ":" + sharingUri.getPort(); + qrUrlBuilder.append(':'); + qrUrlBuilder.append(sharingUri.getPort()); } - qrUriString += sharingUri.getPath(); + qrUrlBuilder.append(sharingUri.getPath()); boolean first = true; - // Andorid provides an API for getting the query parameters and iterating over them: - // Uri.getQueryParameterNames() - // But it is only available on later Android versions. As such we use URLEncodedUtils instead. - List parameters = URLEncodedUtils.parse(URI.create(sharingUri.toString()), "UTF-8"); - for (NameValuePair parameter : parameters) { - if (!"ssid".equals(parameter.getName())) { - if (first) { - qrUriString += "?"; - first = false; - } else { - qrUriString += "&"; + if (Build.VERSION.SDK_INT > 10) { + 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 += parameter.getName().toUpperCase(Locale.ENGLISH) + "=" + - parameter.getValue().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); } diff --git a/app/src/main/res/layout/swap_blank.xml b/app/src/main/res/layout/swap_blank.xml index 6266be168..55493c486 100644 --- a/app/src/main/res/layout/swap_blank.xml +++ b/app/src/main/res/layout/swap_blank.xml @@ -58,7 +58,7 @@ @@ -114,7 +114,7 @@ diff --git a/app/src/main/res/layout/swap_send_fdroid.xml b/app/src/main/res/layout/swap_send_fdroid.xml new file mode 100644 index 000000000..79f1736e5 --- /dev/null +++ b/app/src/main/res/layout/swap_send_fdroid.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + +