1package eu.siacs.conversations.persistance;
2
3import java.io.File;
4import java.io.FileInputStream;
5import java.io.FileNotFoundException;
6import java.io.FileOutputStream;
7import java.io.IOException;
8import java.io.InputStream;
9import java.io.OutputStream;
10
11import org.bouncycastle.crypto.engines.ISAACEngine;
12
13import android.content.Context;
14import android.graphics.Bitmap;
15import android.graphics.BitmapFactory;
16import android.net.Uri;
17import android.util.Log;
18import android.util.LruCache;
19import eu.siacs.conversations.R;
20import eu.siacs.conversations.entities.Conversation;
21import eu.siacs.conversations.entities.Message;
22import eu.siacs.conversations.xmpp.jingle.JingleFile;
23
24public class FileBackend {
25
26 private static int IMAGE_SIZE = 1920;
27
28 private Context context;
29 private LruCache<String, Bitmap> thumbnailCache;
30
31 public FileBackend(Context context) {
32 this.context = context;
33 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
34 int cacheSize = maxMemory / 8;
35 thumbnailCache = new LruCache<String, Bitmap>(cacheSize) {
36 @Override
37 protected int sizeOf(String key, Bitmap bitmap) {
38 return bitmap.getByteCount() / 1024;
39 }
40 };
41
42 }
43
44 public LruCache<String, Bitmap> getThumbnailCache() {
45 return thumbnailCache;
46 }
47
48 public JingleFile getJingleFile(Message message) {
49 return getJingleFile(message, true);
50 }
51
52 public JingleFile getJingleFile(Message message, boolean decrypted) {
53 Conversation conversation = message.getConversation();
54 String prefix = context.getFilesDir().getAbsolutePath();
55 String path = prefix + "/" + conversation.getAccount().getJid() + "/"
56 + conversation.getContactJid();
57 String filename;
58 if ((decrypted) || (message.getEncryption() == Message.ENCRYPTION_NONE)) {
59 filename = message.getUuid() + ".webp";
60 } else {
61 filename = message.getUuid() + ".webp.pgp";
62 }
63 return new JingleFile(path + "/" + filename);
64 }
65
66 public Bitmap resize(Bitmap originalBitmap, int size) {
67 int w = originalBitmap.getWidth();
68 int h = originalBitmap.getHeight();
69 if (Math.max(w, h) > size) {
70 int scalledW;
71 int scalledH;
72 if (w <= h) {
73 scalledW = (int) (w / ((double) h / size));
74 scalledH = size;
75 } else {
76 scalledW = size;
77 scalledH = (int) (h / ((double) w / size));
78 }
79 Bitmap scalledBitmap = Bitmap.createScaledBitmap(originalBitmap,
80 scalledW, scalledH, true);
81 return scalledBitmap;
82 } else {
83 return originalBitmap;
84 }
85 }
86
87 public JingleFile copyImageToPrivateStorage(Message message, Uri image) throws ImageCopyException {
88 return this.copyImageToPrivateStorage(message, image,0);
89 }
90
91 private JingleFile copyImageToPrivateStorage(Message message, Uri image, int sampleSize)
92 throws ImageCopyException {
93 try {
94 InputStream is;
95 if (image != null) {
96 is = context.getContentResolver().openInputStream(image);
97 } else {
98 is = new FileInputStream(getIncomingFile());
99 }
100 JingleFile file = getJingleFile(message);
101 file.getParentFile().mkdirs();
102 file.createNewFile();
103 Bitmap originalBitmap;
104 BitmapFactory.Options options = new BitmapFactory.Options();
105 int inSampleSize = (int) Math.pow(2, sampleSize);
106 Log.d("xmppService","reading bitmap with sample size "+inSampleSize);
107 options.inSampleSize = inSampleSize;
108 originalBitmap = BitmapFactory.decodeStream(is, null, options);
109 is.close();
110 if (originalBitmap == null) {
111 throw new ImageCopyException(R.string.error_not_an_image_file);
112 }
113 if (image == null) {
114 getIncomingFile().delete();
115 }
116 Bitmap scalledBitmap = resize(originalBitmap, IMAGE_SIZE);
117 OutputStream os = new FileOutputStream(file);
118 boolean success = scalledBitmap.compress(
119 Bitmap.CompressFormat.WEBP, 75, os);
120 if (!success) {
121 throw new ImageCopyException(R.string.error_compressing_image);
122 }
123 os.flush();
124 os.close();
125 long size = file.getSize();
126 int width = scalledBitmap.getWidth();
127 int height = scalledBitmap.getHeight();
128 message.setBody("" + size + "," + width + "," + height);
129 return file;
130 } catch (FileNotFoundException e) {
131 throw new ImageCopyException(R.string.error_file_not_found);
132 } catch (IOException e) {
133 throw new ImageCopyException(R.string.error_io_exception);
134 } catch (SecurityException e) {
135 throw new ImageCopyException(
136 R.string.error_security_exception_during_image_copy);
137 } catch (OutOfMemoryError e) {
138 ++sampleSize;
139 if (sampleSize<=3) {
140 return copyImageToPrivateStorage(message, image, sampleSize);
141 } else {
142 throw new ImageCopyException(R.string.error_out_of_memory);
143 }
144 }
145 }
146
147 public Bitmap getImageFromMessage(Message message) {
148 return BitmapFactory.decodeFile(getJingleFile(message)
149 .getAbsolutePath());
150 }
151
152 public Bitmap getThumbnail(Message message, int size, boolean cacheOnly)
153 throws FileNotFoundException {
154 Bitmap thumbnail = thumbnailCache.get(message.getUuid());
155 if ((thumbnail == null) && (!cacheOnly)) {
156 Bitmap fullsize = BitmapFactory.decodeFile(getJingleFile(message)
157 .getAbsolutePath());
158 if (fullsize == null) {
159 throw new FileNotFoundException();
160 }
161 thumbnail = resize(fullsize, size);
162 this.thumbnailCache.put(message.getUuid(), thumbnail);
163 }
164 return thumbnail;
165 }
166
167 public void removeFiles(Conversation conversation) {
168 String prefix = context.getFilesDir().getAbsolutePath();
169 String path = prefix + "/" + conversation.getAccount().getJid() + "/"
170 + conversation.getContactJid();
171 File file = new File(path);
172 try {
173 this.deleteFile(file);
174 } catch (IOException e) {
175 Log.d("xmppService",
176 "error deleting file: " + file.getAbsolutePath());
177 }
178 }
179
180 private void deleteFile(File f) throws IOException {
181 if (f.isDirectory()) {
182 for (File c : f.listFiles())
183 deleteFile(c);
184 }
185 f.delete();
186 }
187
188 public File getIncomingFile() {
189 return new File(context.getFilesDir().getAbsolutePath() + "/incoming");
190 }
191
192 public class ImageCopyException extends Exception {
193 private static final long serialVersionUID = -1010013599132881427L;
194 private int resId;
195
196 public ImageCopyException(int resId) {
197 this.resId = resId;
198 }
199
200 public int getResId() {
201 return resId;
202 }
203 }
204}