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.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}