FileBackend.java

  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}