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)
86 throws ImageCopyException {
87 try {
88 InputStream is;
89 if (image != null) {
90 is = context.getContentResolver().openInputStream(image);
91 } else {
92 is = new FileInputStream(getIncomingFile());
93 }
94 JingleFile file = getJingleFile(message);
95 file.getParentFile().mkdirs();
96 file.createNewFile();
97 Bitmap originalBitmap;
98 try {
99 originalBitmap = BitmapFactory.decodeStream(is);
100 is.close();
101 } catch (OutOfMemoryError e) {
102 is.close();
103 Log.d("xmppService","catched out of memory. try again");
104 if (image != null) {
105 is = context.getContentResolver().openInputStream(image);
106 } else {
107 is = new FileInputStream(getIncomingFile());
108 }
109 BitmapFactory.Options options = new BitmapFactory.Options();
110 options.inSampleSize = 2;
111 originalBitmap = BitmapFactory.decodeStream(is, null, options);
112 }
113 if (originalBitmap == null) {
114 throw new ImageCopyException(R.string.error_not_an_image_file);
115 }
116 if (image == null) {
117 getIncomingFile().delete();
118 }
119 Bitmap scalledBitmap = resize(originalBitmap, IMAGE_SIZE);
120 OutputStream os = new FileOutputStream(file);
121 boolean success = scalledBitmap.compress(
122 Bitmap.CompressFormat.WEBP, 75, os);
123 if (!success) {
124 throw new ImageCopyException(R.string.error_compressing_image);
125 }
126 os.flush();
127 os.close();
128 long size = file.getSize();
129 int width = scalledBitmap.getWidth();
130 int height = scalledBitmap.getHeight();
131 message.setBody("" + size + "," + width + "," + height);
132 return file;
133 } catch (FileNotFoundException e) {
134 throw new ImageCopyException(R.string.error_file_not_found);
135 } catch (IOException e) {
136 throw new ImageCopyException(R.string.error_io_exception);
137 } catch (SecurityException e) {
138 throw new ImageCopyException(
139 R.string.error_security_exception_during_image_copy);
140 }
141 }
142
143 public Bitmap getImageFromMessage(Message message) {
144 return BitmapFactory.decodeFile(getJingleFile(message)
145 .getAbsolutePath());
146 }
147
148 public Bitmap getThumbnail(Message message, int size, boolean cacheOnly)
149 throws FileNotFoundException {
150 Bitmap thumbnail = thumbnailCache.get(message.getUuid());
151 if ((thumbnail == null) && (!cacheOnly)) {
152 Bitmap fullsize = BitmapFactory.decodeFile(getJingleFile(message)
153 .getAbsolutePath());
154 if (fullsize == null) {
155 throw new FileNotFoundException();
156 }
157 thumbnail = resize(fullsize, size);
158 this.thumbnailCache.put(message.getUuid(), thumbnail);
159 }
160 return thumbnail;
161 }
162
163 public void removeFiles(Conversation conversation) {
164 String prefix = context.getFilesDir().getAbsolutePath();
165 String path = prefix + "/" + conversation.getAccount().getJid() + "/"
166 + conversation.getContactJid();
167 File file = new File(path);
168 try {
169 this.deleteFile(file);
170 } catch (IOException e) {
171 Log.d("xmppService",
172 "error deleting file: " + file.getAbsolutePath());
173 }
174 }
175
176 private void deleteFile(File f) throws IOException {
177 if (f.isDirectory()) {
178 for (File c : f.listFiles())
179 deleteFile(c);
180 }
181 f.delete();
182 }
183
184 public File getIncomingFile() {
185 return new File(context.getFilesDir().getAbsolutePath() + "/incoming");
186 }
187
188 public class ImageCopyException extends Exception {
189 private static final long serialVersionUID = -1010013599132881427L;
190 private int resId;
191
192 public ImageCopyException(int resId) {
193 this.resId = resId;
194 }
195
196 public int getResId() {
197 return resId;
198 }
199 }
200}