BarcodeProvider.java

  1package eu.siacs.conversations.services;
  2
  3import android.content.ComponentName;
  4import android.content.ContentProvider;
  5import android.content.ContentValues;
  6import android.content.Context;
  7import android.content.Intent;
  8import android.content.ServiceConnection;
  9import android.database.Cursor;
 10import android.graphics.Bitmap;
 11import android.graphics.Color;
 12import android.net.Uri;
 13import android.os.CancellationSignal;
 14import android.os.IBinder;
 15import android.os.ParcelFileDescriptor;
 16import android.support.annotation.Nullable;
 17import android.util.Log;
 18
 19import com.google.zxing.BarcodeFormat;
 20import com.google.zxing.EncodeHintType;
 21import com.google.zxing.common.BitMatrix;
 22import com.google.zxing.qrcode.QRCodeWriter;
 23import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
 24
 25import java.io.File;
 26import java.io.FileNotFoundException;
 27import java.io.FileOutputStream;
 28import java.io.OutputStream;
 29import java.util.Hashtable;
 30
 31import eu.siacs.conversations.Config;
 32import eu.siacs.conversations.entities.Account;
 33import eu.siacs.conversations.utils.CryptoHelper;
 34import rocks.xmpp.addr.Jid;
 35
 36public class BarcodeProvider extends ContentProvider implements ServiceConnection {
 37
 38	private static final String AUTHORITY = ".barcodes";
 39
 40	private final Object lock = new Object();
 41
 42	private XmppConnectionService mXmppConnectionService;
 43	private boolean mBindingInProcess = false;
 44
 45	public static Uri getUriForAccount(Context context, Account account) {
 46		final String packageId = context.getPackageName();
 47		return Uri.parse("content://" + packageId + AUTHORITY + "/" + account.getJid().asBareJid() + ".png");
 48	}
 49
 50	public static Bitmap create2dBarcodeBitmap(String input, int size) {
 51		try {
 52			final QRCodeWriter barcodeWriter = new QRCodeWriter();
 53			final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
 54			hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
 55			hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
 56			final BitMatrix result = barcodeWriter.encode(input, BarcodeFormat.QR_CODE, size, size, hints);
 57			final int width = result.getWidth();
 58			final int height = result.getHeight();
 59			final int[] pixels = new int[width * height];
 60			for (int y = 0; y < height; y++) {
 61				final int offset = y * width;
 62				for (int x = 0; x < width; x++) {
 63					pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.WHITE;
 64				}
 65			}
 66			final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
 67			bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
 68			return bitmap;
 69		} catch (final Exception e) {
 70			e.printStackTrace();
 71			return null;
 72		}
 73	}
 74
 75	@Override
 76	public boolean onCreate() {
 77		File barcodeDirectory = new File(getContext().getCacheDir().getAbsolutePath() + "/barcodes/");
 78		if (barcodeDirectory.exists() && barcodeDirectory.isDirectory()) {
 79			for (File file : barcodeDirectory.listFiles()) {
 80				if (file.isFile() && !file.isHidden()) {
 81					Log.d(Config.LOGTAG, "deleting old barcode file " + file.getAbsolutePath());
 82					file.delete();
 83				}
 84			}
 85		}
 86		return true;
 87	}
 88
 89	@Nullable
 90	@Override
 91	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
 92		return null;
 93	}
 94
 95	@Nullable
 96	@Override
 97	public String getType(Uri uri) {
 98		return "image/png";
 99	}
100
101	@Nullable
102	@Override
103	public Uri insert(Uri uri, ContentValues values) {
104		return null;
105	}
106
107	@Override
108	public int delete(Uri uri, String selection, String[] selectionArgs) {
109		return 0;
110	}
111
112	@Override
113	public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
114		return 0;
115	}
116
117	@Override
118	public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
119		return openFile(uri, mode, null);
120	}
121
122	@Override
123	public ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal) throws FileNotFoundException {
124		Log.d(Config.LOGTAG, "opening file with uri (normal): " + uri.toString());
125		String path = uri.getPath();
126		if (path != null && path.endsWith(".png") && path.length() >= 5) {
127			String jid = path.substring(1).substring(0, path.length() - 4);
128			Log.d(Config.LOGTAG, "account:" + jid);
129			if (connectAndWait()) {
130				Log.d(Config.LOGTAG, "connected to background service");
131				try {
132					Account account = mXmppConnectionService.findAccountByJid(Jid.of(jid));
133					if (account != null) {
134						String shareableUri = account.getShareableUri();
135						String hash = CryptoHelper.getFingerprint(shareableUri);
136						File file = new File(getContext().getCacheDir().getAbsolutePath() + "/barcodes/" + hash);
137						if (!file.exists()) {
138							file.getParentFile().mkdirs();
139							file.createNewFile();
140							Bitmap bitmap = create2dBarcodeBitmap(account.getShareableUri(), 1024);
141							OutputStream outputStream = new FileOutputStream(file);
142							bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
143							outputStream.close();
144							outputStream.flush();
145						}
146						return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
147					}
148				} catch (Exception e) {
149					throw new FileNotFoundException();
150				}
151			}
152		}
153		throw new FileNotFoundException();
154	}
155
156	private boolean connectAndWait() {
157		Intent intent = new Intent(getContext(), XmppConnectionService.class);
158		intent.setAction(this.getClass().getSimpleName());
159		Context context = getContext();
160		if (context != null) {
161			synchronized (this) {
162				if (mXmppConnectionService == null && !mBindingInProcess) {
163					Log.d(Config.LOGTAG, "calling to bind service");
164					context.startService(intent);
165					context.bindService(intent, this, Context.BIND_AUTO_CREATE);
166					this.mBindingInProcess = true;
167				}
168			}
169			try {
170				waitForService();
171				return true;
172			} catch (InterruptedException e) {
173				return false;
174			}
175		} else {
176			Log.d(Config.LOGTAG, "context was null");
177			return false;
178		}
179	}
180
181	@Override
182	public void onServiceConnected(ComponentName name, IBinder service) {
183		synchronized (this) {
184			XmppConnectionService.XmppConnectionBinder binder = (XmppConnectionService.XmppConnectionBinder) service;
185			mXmppConnectionService = binder.getService();
186			mBindingInProcess = false;
187			synchronized (this.lock) {
188				lock.notifyAll();
189			}
190		}
191	}
192
193	@Override
194	public void onServiceDisconnected(ComponentName name) {
195		synchronized (this) {
196			mXmppConnectionService = null;
197		}
198	}
199
200	private void waitForService() throws InterruptedException {
201		if (mXmppConnectionService == null) {
202			synchronized (this.lock) {
203				lock.wait();
204			}
205		} else {
206			Log.d(Config.LOGTAG, "not waiting for service because already initialized");
207		}
208	}
209}