diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/KeyTransferClientInteractor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/KeyTransferClientInteractor.java
new file mode 100644
index 000000000..69bf29e0b
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/KeyTransferClientInteractor.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2017 Tobias Schülke
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.sufficientlysecure.keychain.network;
+
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.List;
+
+import android.net.PskKeyManager;
+import android.os.Build.VERSION_CODES;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.RequiresApi;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.Log;
+
+
+@RequiresApi(api = VERSION_CODES.LOLLIPOP)
+public class KeyTransferClientInteractor {
+ private static final int SHOW_CONNECTION_DETAILS = 1;
+ private static final int CONNECTION_ESTABLISHED = 2;
+ public static final int CONNECTION_LOST = 3;
+
+
+ private Thread socketThread;
+ private KeyTransferClientCallback callback;
+ private Handler handler;
+ private SSLServerSocket serverSocket;
+
+
+ public void connectToServer(final String connectionDetails, KeyTransferClientCallback callback) {
+ this.callback = callback;
+
+ handler = new Handler(Looper.getMainLooper());
+ socketThread = new Thread() {
+ @Override
+ public void run() {
+ serverSocket = null;
+ Socket socket = null;
+ BufferedReader bufferedReader = null;
+ try {
+ int port = 1336;
+
+ PKM pskKeyManager = new PKM();
+ SSLContext sslContext = null;
+ sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(new KeyManager[] { pskKeyManager }, new TrustManager[0], null);
+ socket = sslContext.getSocketFactory().createSocket(InetAddress.getByName(connectionDetails), port);
+
+ invokeListener(CONNECTION_ESTABLISHED, socket.getInetAddress().toString());
+
+ socket.setSoTimeout(500);
+ bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+ while (!isInterrupted() && socket.isConnected()) {
+ try {
+ String line = bufferedReader.readLine();
+ if (line == null) {
+ break;
+ }
+ Log.d(Constants.TAG, "got line: " + line);
+ } catch (SocketTimeoutException e) {
+ // ignore
+ }
+ }
+ Log.d(Constants.TAG, "disconnected");
+ invokeListener(CONNECTION_LOST, null);
+ } catch (NoSuchAlgorithmException | KeyManagementException | IOException e) {
+ Log.e(Constants.TAG, "error!", e);
+ } finally {
+ try {
+ if (bufferedReader != null) {
+ bufferedReader.close();
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ try {
+ if (socket != null) {
+ socket.close();
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ try {
+ if (serverSocket != null) {
+ serverSocket.close();
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ };
+
+ socketThread.start();
+ }
+
+ public void closeConnection() {
+ if (socketThread != null) {
+ socketThread.interrupt();
+ }
+
+ socketThread = null;
+ callback = null;
+ }
+
+ private void invokeListener(final int method, final String arg) {
+ if (handler == null) {
+ return;
+ }
+
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ if (callback == null) {
+ return;
+ }
+
+ switch (method) {
+ case CONNECTION_ESTABLISHED:
+ callback.onConnectionEstablished(arg);
+ break;
+ case CONNECTION_LOST:
+ callback.onConnectionLost();
+ }
+ }
+ };
+
+ handler.post(runnable);
+ }
+
+ public interface KeyTransferClientCallback {
+ void onConnectionEstablished(String otherName);
+ void onConnectionLost();
+ }
+
+ private static class PKM extends PskKeyManager implements KeyManager {
+ @Override
+ public SecretKey getKey(String identityHint, String identity, Socket socket) {
+ return new SecretKeySpec("swag".getBytes(), "AES");
+ }
+
+ @Override
+ public SecretKey getKey(String identityHint, String identity, SSLEngine engine) {
+ return new SecretKeySpec("swag".getBytes(), "AES");
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/KeyTransferServerInteractor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/KeyTransferServerInteractor.java
new file mode 100644
index 000000000..e0d27ffff
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/network/KeyTransferServerInteractor.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2017 Tobias Schülke
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.sufficientlysecure.keychain.network;
+
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.List;
+
+import android.net.PskKeyManager;
+import android.os.Build.VERSION_CODES;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.RequiresApi;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.TrustManager;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.Log;
+
+
+@RequiresApi(api = VERSION_CODES.LOLLIPOP)
+public class KeyTransferServerInteractor {
+ private static final int SHOW_CONNECTION_DETAILS = 1;
+ private static final int CONNECTION_ESTABLISHED = 2;
+ public static final int CONNECTION_LOST = 3;
+
+
+ private Thread socketThread;
+ private KeyTransferServerCallback callback;
+ private Handler handler;
+ private SSLServerSocket serverSocket;
+
+ public void startServer(KeyTransferServerCallback callback) {
+ this.callback = callback;
+
+ handler = new Handler(Looper.getMainLooper());
+ socketThread = new Thread() {
+ @Override
+ public void run() {
+ serverSocket = null;
+ Socket socket = null;
+ BufferedReader bufferedReader = null;
+ try {
+ int port = 1336;
+
+ PKM pskKeyManager = new PKM();
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(new KeyManager[] { pskKeyManager }, new TrustManager[0], null);
+ serverSocket = (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(port);
+
+ String qrCodeData = getIPAddress(true) + ":" + port + ":" + "swag";
+ invokeListener(SHOW_CONNECTION_DETAILS, qrCodeData);
+
+ socket = serverSocket.accept();
+ invokeListener(CONNECTION_ESTABLISHED, socket.getInetAddress().toString());
+
+ socket.setSoTimeout(500);
+ bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+ while (!isInterrupted() && socket.isConnected()) {
+ try {
+ String line = bufferedReader.readLine();
+ if (line == null) {
+ break;
+ }
+ Log.d(Constants.TAG, "got line: " + line);
+ } catch (SocketTimeoutException e) {
+ // ignore
+ }
+ }
+ Log.d(Constants.TAG, "disconnected");
+ invokeListener(CONNECTION_LOST, null);
+ } catch (NoSuchAlgorithmException | KeyManagementException | IOException e) {
+ Log.e(Constants.TAG, "error!", e);
+ } finally {
+ try {
+ if (bufferedReader != null) {
+ bufferedReader.close();
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ try {
+ if (socket != null) {
+ socket.close();
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ try {
+ if (serverSocket != null) {
+ serverSocket.close();
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ };
+
+ socketThread.start();
+ }
+
+ public void stopServer() {
+ if (socketThread != null) {
+ socketThread.interrupt();
+ }
+
+ if (serverSocket != null) {
+ try {
+ serverSocket.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ socketThread = null;
+ serverSocket = null;
+ callback = null;
+ }
+
+ private void invokeListener(final int method, final String arg) {
+ if (handler == null) {
+ return;
+ }
+
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ switch (method) {
+ case SHOW_CONNECTION_DETAILS:
+ callback.onServerStarted(arg);
+ break;
+ case CONNECTION_ESTABLISHED:
+ callback.onConnectionEstablished(arg);
+ break;
+ case CONNECTION_LOST:
+ callback.onConnectionLost();
+ }
+ }
+ };
+
+ handler.post(runnable);
+ }
+
+ public interface KeyTransferServerCallback {
+ void onServerStarted(String qrCodeData);
+ void onConnectionEstablished(String otherName);
+ void onConnectionLost();
+ }
+
+ /**
+ * from: http://stackoverflow.com/a/13007325
+ *
+ * Get IP address from first non-localhost interface
+ *
+ * @param useIPv4 true=return ipv4, false=return ipv6
+ * @return address or empty string
+ */
+ private static String getIPAddress(boolean useIPv4) {
+ try {
+ List interfaces = Collections.list(NetworkInterface.
+ getNetworkInterfaces());
+ for (NetworkInterface intf : interfaces) {
+ List addrs = Collections.list(intf.getInetAddresses());
+ for (InetAddress addr : addrs) {
+ if (!addr.isLoopbackAddress()) {
+ String sAddr = addr.getHostAddress();
+ boolean isIPv4 = sAddr.indexOf(':') < 0;
+
+ if (useIPv4) {
+ if (isIPv4)
+ return sAddr;
+ } else {
+ if (!isIPv4) {
+ int delim = sAddr.indexOf('%'); // drop ip6 zone suffix
+ return delim < 0 ? sAddr.toUpperCase() : sAddr.substring(0, delim).
+ toUpperCase();
+ }
+ }
+ }
+ }
+ }
+ } catch (Exception ex) {
+ } // for now eat exceptions
+ return "";
+ }
+
+ private static class PKM extends PskKeyManager implements KeyManager {
+ @Override
+ public SecretKey getKey(String identityHint, String identity, Socket socket) {
+ return new SecretKeySpec("swag".getBytes(), "AES");
+ }
+
+ @Override
+ public SecretKey getKey(String identityHint, String identity, SSLEngine engine) {
+ return new SecretKeySpec("swag".getBytes(), "AES");
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
index 13df0b539..a34d470bc 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
@@ -41,6 +41,7 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.remote.ui.AppsListFragment;
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
+import org.sufficientlysecure.keychain.ui.transfer.view.TransferFragment;
import org.sufficientlysecure.keychain.util.FabContainer;
import org.sufficientlysecure.keychain.util.Preferences;
@@ -50,8 +51,9 @@ public class MainActivity extends BaseSecurityTokenActivity implements FabContai
static final int ID_ENCRYPT_DECRYPT = 2;
static final int ID_APPS = 3;
static final int ID_BACKUP = 4;
- static final int ID_SETTINGS = 5;
- static final int ID_HELP = 6;
+ static final int ID_TRANSFER = 5;
+ static final int ID_SETTINGS = 6;
+ static final int ID_HELP = 7;
// both of these are used for instrumentation testing only
public static final String EXTRA_SKIP_FIRST_TIME = "skip_first_time";
@@ -82,6 +84,8 @@ public class MainActivity extends BaseSecurityTokenActivity implements FabContai
.withIdentifier(ID_APPS).withSelectable(false),
new PrimaryDrawerItem().withName(R.string.nav_backup).withIcon(CommunityMaterial.Icon.cmd_backup_restore)
.withIdentifier(ID_BACKUP).withSelectable(false),
+ new PrimaryDrawerItem().withName(R.string.nav_transfer).withIcon(CommunityMaterial.Icon.cmd_backup_restore)
+ .withIdentifier(ID_TRANSFER).withSelectable(false),
new DividerDrawerItem(),
new PrimaryDrawerItem().withName(R.string.menu_preferences).withIcon(GoogleMaterial.Icon.gmd_settings).withIdentifier(ID_SETTINGS).withSelectable(false),
new PrimaryDrawerItem().withName(R.string.menu_help).withIcon(CommunityMaterial.Icon.cmd_help_circle).withIdentifier(ID_HELP).withSelectable(false)
@@ -105,6 +109,9 @@ public class MainActivity extends BaseSecurityTokenActivity implements FabContai
case ID_BACKUP:
onBackupSelected();
break;
+ case ID_TRANSFER:
+ onTransferSelected();
+ break;
case ID_SETTINGS:
intent = new Intent(MainActivity.this, SettingsActivity.class);
break;
@@ -207,6 +214,13 @@ public class MainActivity extends BaseSecurityTokenActivity implements FabContai
setFragment(frag, true);
}
+ private void onTransferSelected() {
+ mToolbar.setTitle(R.string.nav_transfer);
+ mDrawer.setSelection(ID_TRANSFER, false);
+ Fragment frag = new TransferFragment();
+ setFragment(frag, true);
+ }
+
@Override
protected void onSaveInstanceState(Bundle outState) {
// add the values which need to be saved from the drawer to the bundle
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/presenter/TransferPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/presenter/TransferPresenter.java
new file mode 100644
index 000000000..b9d981c0a
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/presenter/TransferPresenter.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 Vincent Breitmoser
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.sufficientlysecure.keychain.ui.transfer.presenter;
+
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build.VERSION_CODES;
+import android.support.annotation.RequiresApi;
+
+import org.sufficientlysecure.keychain.network.KeyTransferClientInteractor;
+import org.sufficientlysecure.keychain.network.KeyTransferClientInteractor.KeyTransferClientCallback;
+import org.sufficientlysecure.keychain.network.KeyTransferServerInteractor;
+import org.sufficientlysecure.keychain.network.KeyTransferServerInteractor.KeyTransferServerCallback;
+import org.sufficientlysecure.keychain.ui.util.QrCodeUtils;
+
+
+@RequiresApi(api = VERSION_CODES.LOLLIPOP)
+public class TransferPresenter implements KeyTransferServerCallback, KeyTransferClientCallback {
+ private final Context context;
+ private final TransferMvpView view;
+
+ private KeyTransferServerInteractor keyTransferServerInteractor;
+ private KeyTransferClientInteractor keyTransferClientInteractor;
+
+ public TransferPresenter(Context context, TransferMvpView view) {
+ this.context = context;
+ this.view = view;
+ }
+
+ public void onDestroy() {
+ clearConnections();
+ }
+
+ public void onClickScan() {
+ clearConnections();
+
+ keyTransferClientInteractor = new KeyTransferClientInteractor();
+ keyTransferClientInteractor.connectToServer("10.100.40.126", this);
+ }
+
+ private void clearConnections() {
+ if (keyTransferServerInteractor != null) {
+ keyTransferServerInteractor.stopServer();
+ keyTransferServerInteractor = null;
+ }
+ if (keyTransferClientInteractor != null) {
+ keyTransferClientInteractor.closeConnection();
+ keyTransferClientInteractor = null;
+ }
+ }
+
+ public void startServer() {
+ keyTransferServerInteractor = new KeyTransferServerInteractor();
+ keyTransferServerInteractor.startServer(this);
+ }
+
+ @Override
+ public void onServerStarted(String qrCodeData) {
+ Bitmap qrCodeBitmap = QrCodeUtils.getQRCodeBitmap(Uri.parse("pgp+transfer:" + qrCodeData), 0);
+ view.setQrImage(qrCodeBitmap);
+ }
+
+ @Override
+ public void onConnectionEstablished(String otherName) {
+ view.showConnectionEstablished(otherName);
+ }
+
+ @Override
+ public void onConnectionLost() {
+ view.showWaitingForConnection();
+ startServer();
+ }
+
+ public interface TransferMvpView {
+ void showConnectionEstablished(String hostname);
+ void showWaitingForConnection();
+
+ void setQrImage(Bitmap qrCode);
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/view/TransferFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/view/TransferFragment.java
new file mode 100644
index 000000000..874eefd98
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/view/TransferFragment.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.sufficientlysecure.keychain.ui.transfer.view;
+
+
+import android.graphics.Bitmap;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.ViewAnimator;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.transfer.presenter.TransferPresenter;
+import org.sufficientlysecure.keychain.ui.transfer.presenter.TransferPresenter.TransferMvpView;
+
+
+@RequiresApi(api = VERSION_CODES.LOLLIPOP)
+public class TransferFragment extends Fragment implements TransferMvpView {
+ public static final int VIEW_WAITING = 0;
+ public static final int VIEW_CONNECTED = 1;
+
+
+ private ImageView vQrCodeImage;
+ private View vScanButton;
+ private TransferPresenter presenter;
+ private ViewAnimator vTransferAnimator;
+ private TextView vConnectionStatusText;
+
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.transfer_fragment, container, false);
+
+ vTransferAnimator = (ViewAnimator) view.findViewById(R.id.transfer_animator);
+
+ vConnectionStatusText = (TextView) view.findViewById(R.id.connection_status);
+
+ vQrCodeImage = (ImageView) view.findViewById(R.id.qr_code_image);
+ vScanButton = view.findViewById(R.id.button_scan);
+ vScanButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (presenter != null) {
+ presenter.onClickScan();
+ }
+ }
+ });
+
+ return view;
+ }
+
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ presenter = new TransferPresenter(getContext(), this);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ presenter.startServer();
+ }
+
+
+ @Override
+ public void onStop() {
+ super.onStop();
+
+ presenter.onDestroy();
+ }
+
+ @Override
+ public void showWaitingForConnection() {
+ vTransferAnimator.setDisplayedChild(VIEW_WAITING);
+ }
+
+ @Override
+ public void showConnectionEstablished(String hostname) {
+ vTransferAnimator.setDisplayedChild(VIEW_CONNECTED);
+ vConnectionStatusText.setText("Connected to: " + hostname);
+ }
+
+ @Override
+ public void setQrImage(final Bitmap qrCode) {
+ vQrCodeImage.getViewTreeObserver().addOnGlobalLayoutListener(
+ new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ // create actual bitmap in display dimensions
+ Bitmap scaled = Bitmap.createScaledBitmap(qrCode,
+ vQrCodeImage.getWidth(), vQrCodeImage.getWidth(), false);
+ vQrCodeImage.setImageBitmap(scaled);
+ }
+ });
+ vQrCodeImage.requestLayout();
+ }
+}
diff --git a/OpenKeychain/src/main/res/layout/transfer_fragment.xml b/OpenKeychain/src/main/res/layout/transfer_fragment.xml
new file mode 100644
index 000000000..b99369751
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/transfer_fragment.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml
index a070d764d..cd28f0126 100644
--- a/OpenKeychain/src/main/res/values/strings.xml
+++ b/OpenKeychain/src/main/res/values/strings.xml
@@ -848,6 +848,7 @@
"Close navigation drawer"
"My Keys"
"Backup/Restore"
+ "Transfer"
"Type text"