added share button to account details
This commit is contained in:
parent
d2268c6a6f
commit
07fe434cc7
|
@ -204,6 +204,11 @@
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/file_paths" />
|
android:resource="@xml/file_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
<provider
|
||||||
|
android:authorities="eu.siacs.conversations.barcodes"
|
||||||
|
android:name=".services.BarcodeProvider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true"/>
|
||||||
|
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
|
@ -0,0 +1,226 @@
|
||||||
|
package eu.siacs.conversations.services;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.ContentProvider;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.CancellationSignal;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
import com.google.zxing.EncodeHintType;
|
||||||
|
import com.google.zxing.aztec.AztecWriter;
|
||||||
|
import com.google.zxing.common.BitMatrix;
|
||||||
|
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.entities.Account;
|
||||||
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
|
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
||||||
|
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||||
|
|
||||||
|
public class BarcodeProvider extends ContentProvider implements ServiceConnection {
|
||||||
|
|
||||||
|
private static final String AUTHORITY = "eu.siacs.conversations.barcodes";
|
||||||
|
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
private XmppConnectionService mXmppConnectionService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreate() {
|
||||||
|
File barcodeDirectory = new File(getContext().getCacheDir().getAbsolutePath() + "/barcodes/");
|
||||||
|
if (barcodeDirectory.exists() && barcodeDirectory.isDirectory()) {
|
||||||
|
for (File file : barcodeDirectory.listFiles()) {
|
||||||
|
if (file.isFile() && !file.isHidden()) {
|
||||||
|
Log.d(Config.LOGTAG, "deleting old barcode file " + file.getAbsolutePath());
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String getType(Uri uri) {
|
||||||
|
return "image/png";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Uri insert(Uri uri, ContentValues values) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||||
|
return openFile(uri, mode, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal) throws FileNotFoundException {
|
||||||
|
Log.d(Config.LOGTAG, "opening file with uri (normal): " + uri.toString());
|
||||||
|
String path = uri.getPath();
|
||||||
|
if (path != null && path.endsWith(".png") && path.length() >= 5) {
|
||||||
|
String jid = path.substring(1).substring(0, path.length() - 4);
|
||||||
|
Log.d(Config.LOGTAG, "account:" + jid);
|
||||||
|
if (connectAndWait()) {
|
||||||
|
Log.d(Config.LOGTAG, "connected to background service");
|
||||||
|
try {
|
||||||
|
Account account = mXmppConnectionService.findAccountByJid(Jid.fromString(jid));
|
||||||
|
if (account != null) {
|
||||||
|
String shareableUri = account.getShareableUri();
|
||||||
|
String hash = CryptoHelper.getFingerprint(shareableUri);
|
||||||
|
File file = new File(getContext().getCacheDir().getAbsolutePath() + "/barcodes/" + hash);
|
||||||
|
if (!file.exists()) {
|
||||||
|
file.getParentFile().mkdirs();
|
||||||
|
file.createNewFile();
|
||||||
|
Bitmap bitmap = createAztecBitmap(account.getShareableUri(), 1024);
|
||||||
|
OutputStream outputStream = new FileOutputStream(file);
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
|
||||||
|
outputStream.close();
|
||||||
|
outputStream.flush();
|
||||||
|
}
|
||||||
|
return ParcelFileDescriptor.open(file,ParcelFileDescriptor.MODE_READ_ONLY);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new FileNotFoundException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new FileNotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean connectAndWait() {
|
||||||
|
Intent intent = new Intent(getContext(), XmppConnectionService.class);
|
||||||
|
intent.setAction("contact_chooser");
|
||||||
|
Context context = getContext();
|
||||||
|
if (context != null) {
|
||||||
|
context.startService(intent);
|
||||||
|
context.bindService(intent, this, Context.BIND_AUTO_CREATE);
|
||||||
|
try {
|
||||||
|
waitForService();
|
||||||
|
Log.d(Config.LOGTAG, "service initialized");
|
||||||
|
return true;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(Config.LOGTAG, "context was null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
|
XmppConnectionService.XmppConnectionBinder binder = (XmppConnectionService.XmppConnectionBinder) service;
|
||||||
|
mXmppConnectionService = binder.getService();
|
||||||
|
synchronized (this.lock) {
|
||||||
|
lock.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
|
mXmppConnectionService = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitForService() throws InterruptedException {
|
||||||
|
if (mXmppConnectionService == null) {
|
||||||
|
synchronized (this.lock) {
|
||||||
|
lock.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Uri getUriForAccount(Account account) {
|
||||||
|
return Uri.parse("content://" + AUTHORITY + "/" + account.getJid().toBareJid() + ".png");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bitmap createAztecBitmap(String input, int size) {
|
||||||
|
try {
|
||||||
|
final AztecWriter AZTEC_WRITER = new AztecWriter();
|
||||||
|
final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
|
||||||
|
hints.put(EncodeHintType.ERROR_CORRECTION, 10);
|
||||||
|
final BitMatrix result = AZTEC_WRITER.encode(input, BarcodeFormat.AZTEC, size, size, hints);
|
||||||
|
final int width = result.getWidth();
|
||||||
|
final int height = result.getHeight();
|
||||||
|
final int[] pixels = new int[width * height];
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
final int offset = y * width;
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||||
|
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
|
||||||
|
return bitmap;
|
||||||
|
} catch (final Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TransferThread extends Thread {
|
||||||
|
InputStream in;
|
||||||
|
OutputStream out;
|
||||||
|
|
||||||
|
TransferThread(InputStream in, OutputStream out) {
|
||||||
|
this.in = in;
|
||||||
|
this.out = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
byte[] buf = new byte[1024];
|
||||||
|
int len;
|
||||||
|
|
||||||
|
try {
|
||||||
|
while ((len = in.read(buf)) >= 0) {
|
||||||
|
out.write(buf, 0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
in.close();
|
||||||
|
out.flush();
|
||||||
|
out.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(Config.LOGTAG, "Exception transferring file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,6 +46,7 @@ import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||||
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
|
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
|
import eu.siacs.conversations.services.BarcodeProvider;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService.OnCaptchaRequested;
|
import eu.siacs.conversations.services.XmppConnectionService.OnCaptchaRequested;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
|
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
|
||||||
|
@ -703,6 +704,15 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
|
||||||
case R.id.action_server_info_show_more:
|
case R.id.action_server_info_show_more:
|
||||||
changeMoreTableVisibility(!item.isChecked());
|
changeMoreTableVisibility(!item.isChecked());
|
||||||
break;
|
break;
|
||||||
|
case R.id.action_share_barcode:
|
||||||
|
shareBarcode();
|
||||||
|
break;
|
||||||
|
case R.id.action_share_http:
|
||||||
|
shareLink(true);
|
||||||
|
break;
|
||||||
|
case R.id.action_share_uri:
|
||||||
|
shareLink(false);
|
||||||
|
break;
|
||||||
case R.id.action_change_password_on_server:
|
case R.id.action_change_password_on_server:
|
||||||
gotoChangePassword(null);
|
gotoChangePassword(null);
|
||||||
break;
|
break;
|
||||||
|
@ -722,6 +732,27 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void shareLink(boolean http) {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||||
|
intent.setType("text/plain");
|
||||||
|
String text;
|
||||||
|
if (http) {
|
||||||
|
text = "https://conversations.im/i/"+mAccount.getJid().toBareJid().toString();
|
||||||
|
} else {
|
||||||
|
text = mAccount.getShareableUri();
|
||||||
|
}
|
||||||
|
intent.putExtra(Intent.EXTRA_TEXT,text);
|
||||||
|
startActivity(Intent.createChooser(intent, getText(R.string.share_with)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shareBarcode() {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||||
|
intent.putExtra(Intent.EXTRA_STREAM,BarcodeProvider.getUriForAccount(mAccount));
|
||||||
|
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
intent.setType("image/png");
|
||||||
|
startActivity(Intent.createChooser(intent, getText(R.string.share_with)));
|
||||||
|
}
|
||||||
|
|
||||||
private void changeMoreTableVisibility(boolean visible) {
|
private void changeMoreTableVisibility(boolean visible) {
|
||||||
mMoreTable.setVisibility(visible ? View.VISIBLE : View.GONE);
|
mMoreTable.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,7 @@ import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.entities.MucOptions;
|
import eu.siacs.conversations.entities.MucOptions;
|
||||||
import eu.siacs.conversations.entities.Presences;
|
import eu.siacs.conversations.entities.Presences;
|
||||||
import eu.siacs.conversations.services.AvatarService;
|
import eu.siacs.conversations.services.AvatarService;
|
||||||
|
import eu.siacs.conversations.services.BarcodeProvider;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
|
import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
|
@ -1058,7 +1059,7 @@ public abstract class XmppActivity extends Activity {
|
||||||
Point size = new Point();
|
Point size = new Point();
|
||||||
getWindowManager().getDefaultDisplay().getSize(size);
|
getWindowManager().getDefaultDisplay().getSize(size);
|
||||||
final int width = (size.x < size.y ? size.x : size.y);
|
final int width = (size.x < size.y ? size.x : size.y);
|
||||||
Bitmap bitmap = createQrCodeBitmap(uri, width);
|
Bitmap bitmap = BarcodeProvider.createAztecBitmap(uri, width);
|
||||||
ImageView view = new ImageView(this);
|
ImageView view = new ImageView(this);
|
||||||
view.setBackgroundColor(Color.WHITE);
|
view.setBackgroundColor(Color.WHITE);
|
||||||
view.setImageBitmap(bitmap);
|
view.setImageBitmap(bitmap);
|
||||||
|
@ -1068,29 +1069,6 @@ public abstract class XmppActivity extends Activity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Bitmap createQrCodeBitmap(String input, int size) {
|
|
||||||
try {
|
|
||||||
final AztecWriter AZTEC_WRITER = new AztecWriter();
|
|
||||||
final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
|
|
||||||
hints.put(EncodeHintType.ERROR_CORRECTION, 10);
|
|
||||||
final BitMatrix result = AZTEC_WRITER.encode(input, BarcodeFormat.AZTEC, size, size, hints);
|
|
||||||
final int width = result.getWidth();
|
|
||||||
final int height = result.getHeight();
|
|
||||||
final int[] pixels = new int[width * height];
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
final int offset = y * width;
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
|
||||||
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
|
|
||||||
return bitmap;
|
|
||||||
} catch (final Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Account extractAccount(Intent intent) {
|
protected Account extractAccount(Intent intent) {
|
||||||
String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null;
|
String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -206,9 +206,13 @@ public final class CryptoHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getAccountFingerprint(Account account) {
|
public static String getAccountFingerprint(Account account) {
|
||||||
|
return getFingerprint(account.getJid().toBareJid().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getFingerprint(String value) {
|
||||||
try {
|
try {
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
return bytesToHex(md.digest(account.getJid().toBareJid().toString().getBytes("UTF-8")));
|
return bytesToHex(md.digest(value.getBytes("UTF-8")));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,21 @@
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item android:id="@+id/action_share"
|
||||||
|
android:title="@string/share_uri_with"
|
||||||
|
android:icon="?attr/icon_share"
|
||||||
|
android:showAsAction="always">
|
||||||
|
<menu>
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_share_barcode"
|
||||||
|
android:title="@string/share_as_barcode"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_share_uri"
|
||||||
|
android:title="@string/share_as_uri"/>
|
||||||
|
<item android:id="@+id/action_share_http"
|
||||||
|
android:title="@string/share_as_http"/>
|
||||||
|
</menu>
|
||||||
|
</item>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_change_presence"
|
android:id="@+id/action_change_presence"
|
||||||
android:showAsAction="always"
|
android:showAsAction="always"
|
||||||
|
|
|
@ -707,4 +707,7 @@
|
||||||
<string name="verified_fingerprints">Verified fingerprints</string>
|
<string name="verified_fingerprints">Verified fingerprints</string>
|
||||||
<string name="use_camera_icon_to_scan_barcode">Use the camera to scan a contacts barcode</string>
|
<string name="use_camera_icon_to_scan_barcode">Use the camera to scan a contacts barcode</string>
|
||||||
<string name="please_wait_for_keys_to_be_fetched">Please wait for keys to be fetched</string>
|
<string name="please_wait_for_keys_to_be_fetched">Please wait for keys to be fetched</string>
|
||||||
|
<string name="share_as_barcode">Share as Barcode</string>
|
||||||
|
<string name="share_as_uri">Share as XMPP URI</string>
|
||||||
|
<string name="share_as_http">Share as HTTP link</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue