BarcodeProvider.java

  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.WHITE;
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}