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