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