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