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}