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.graphics.Matrix;
15import android.media.ExifInterface;
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 if (message.getEncryption() == Message.ENCRYPTION_OTR) {
62 filename = message.getUuid() + ".webp";
63 } else {
64 filename = message.getUuid() + ".webp.pgp";
65 }
66 }
67 return new JingleFile(path + "/" + filename);
68 }
69
70 public Bitmap resize(Bitmap originalBitmap, int size) {
71 int w = originalBitmap.getWidth();
72 int h = originalBitmap.getHeight();
73 if (Math.max(w, h) > size) {
74 int scalledW;
75 int scalledH;
76 if (w <= h) {
77 scalledW = (int) (w / ((double) h / size));
78 scalledH = size;
79 } else {
80 scalledW = size;
81 scalledH = (int) (h / ((double) w / size));
82 }
83 Bitmap scalledBitmap = Bitmap.createScaledBitmap(originalBitmap,
84 scalledW, scalledH, true);
85 return scalledBitmap;
86 } else {
87 return originalBitmap;
88 }
89 }
90
91 public Bitmap rotate(Bitmap bitmap, int degree) {
92 int w = bitmap.getWidth();
93 int h = bitmap.getHeight();
94 Matrix mtx = new Matrix();
95 mtx.postRotate(degree);
96 return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
97 }
98
99 public JingleFile copyImageToPrivateStorage(Message message, Uri image)
100 throws ImageCopyException {
101 return this.copyImageToPrivateStorage(message, image, 0);
102 }
103
104 private JingleFile copyImageToPrivateStorage(Message message, Uri image,
105 int sampleSize) throws ImageCopyException {
106 try {
107 InputStream is;
108 if (image != null) {
109 is = context.getContentResolver().openInputStream(image);
110 } else {
111 is = new FileInputStream(getIncomingFile());
112 image = getIncomingUri();
113 }
114 JingleFile file = getJingleFile(message);
115 file.getParentFile().mkdirs();
116 file.createNewFile();
117 Bitmap originalBitmap;
118 BitmapFactory.Options options = new BitmapFactory.Options();
119 int inSampleSize = (int) Math.pow(2, sampleSize);
120 Log.d("xmppService", "reading bitmap with sample size "
121 + inSampleSize);
122 options.inSampleSize = inSampleSize;
123 originalBitmap = BitmapFactory.decodeStream(is, null, options);
124 is.close();
125 if (originalBitmap == null) {
126 throw new ImageCopyException(R.string.error_not_an_image_file);
127 }
128 if (image == null) {
129 getIncomingFile().delete();
130 }
131 Bitmap scalledBitmap = resize(originalBitmap, IMAGE_SIZE);
132 originalBitmap = null;
133 ExifInterface exif = new ExifInterface(image.toString());
134 if (exif.getAttribute(ExifInterface.TAG_ORIENTATION)
135 .equalsIgnoreCase("6")) {
136 scalledBitmap = rotate(scalledBitmap, 90);
137 } else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION)
138 .equalsIgnoreCase("8")) {
139 scalledBitmap = rotate(scalledBitmap, 270);
140 } else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION)
141 .equalsIgnoreCase("3")) {
142 scalledBitmap = rotate(scalledBitmap, 180);
143 }
144 OutputStream os = new FileOutputStream(file);
145 boolean success = scalledBitmap.compress(
146 Bitmap.CompressFormat.WEBP, 75, os);
147 if (!success) {
148 throw new ImageCopyException(R.string.error_compressing_image);
149 }
150 os.flush();
151 os.close();
152 long size = file.getSize();
153 int width = scalledBitmap.getWidth();
154 int height = scalledBitmap.getHeight();
155 message.setBody("" + size + "," + width + "," + height);
156 return file;
157 } catch (FileNotFoundException e) {
158 throw new ImageCopyException(R.string.error_file_not_found);
159 } catch (IOException e) {
160 throw new ImageCopyException(R.string.error_io_exception);
161 } catch (SecurityException e) {
162 throw new ImageCopyException(
163 R.string.error_security_exception_during_image_copy);
164 } catch (OutOfMemoryError e) {
165 ++sampleSize;
166 if (sampleSize <= 3) {
167 return copyImageToPrivateStorage(message, image, sampleSize);
168 } else {
169 throw new ImageCopyException(R.string.error_out_of_memory);
170 }
171 }
172 }
173
174 public Bitmap getImageFromMessage(Message message) {
175 return BitmapFactory.decodeFile(getJingleFile(message)
176 .getAbsolutePath());
177 }
178
179 public Bitmap getThumbnail(Message message, int size, boolean cacheOnly)
180 throws FileNotFoundException {
181 Bitmap thumbnail = thumbnailCache.get(message.getUuid());
182 if ((thumbnail == null) && (!cacheOnly)) {
183 Bitmap fullsize = BitmapFactory.decodeFile(getJingleFile(message)
184 .getAbsolutePath());
185 if (fullsize == null) {
186 throw new FileNotFoundException();
187 }
188 thumbnail = resize(fullsize, size);
189 this.thumbnailCache.put(message.getUuid(), thumbnail);
190 }
191 return thumbnail;
192 }
193
194 public void removeFiles(Conversation conversation) {
195 String prefix = context.getFilesDir().getAbsolutePath();
196 String path = prefix + "/" + conversation.getAccount().getJid() + "/"
197 + conversation.getContactJid();
198 File file = new File(path);
199 try {
200 this.deleteFile(file);
201 } catch (IOException e) {
202 Log.d("xmppService",
203 "error deleting file: " + file.getAbsolutePath());
204 }
205 }
206
207 private void deleteFile(File f) throws IOException {
208 if (f.isDirectory()) {
209 for (File c : f.listFiles())
210 deleteFile(c);
211 }
212 f.delete();
213 }
214
215 public File getIncomingFile() {
216 return new File(context.getFilesDir().getAbsolutePath() + "/incoming");
217 }
218
219 public Uri getIncomingUri() {
220 return Uri.parse(context.getFilesDir().getAbsolutePath() + "/incoming");
221 }
222
223 public class ImageCopyException extends Exception {
224 private static final long serialVersionUID = -1010013599132881427L;
225 private int resId;
226
227 public ImageCopyException(int resId) {
228 this.resId = resId;
229 }
230
231 public int getResId() {
232 return resId;
233 }
234 }
235}