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