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