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    @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}