don’t scall images to a 0 width or height

This commit is contained in:
Daniel Gultsch 2018-05-07 11:13:46 +02:00
parent 7ca719b8be
commit 78b56bb904
3 changed files with 320 additions and 318 deletions

View File

@ -77,6 +77,293 @@ public class FileBackend {
this.mXmppConnectionService = service;
}
private static boolean isInDirectoryThatShouldNotBeScanned(Context context, File file) {
return isInDirectoryThatShouldNotBeScanned(context, file.getAbsolutePath());
}
public static boolean isInDirectoryThatShouldNotBeScanned(Context context, String path) {
for (String type : new String[]{RecordingActivity.STORAGE_DIRECTORY_TYPE_NAME, "Files"}) {
if (path.startsWith(getConversationsDirectory(context, type))) {
return true;
}
}
return false;
}
public static long getFileSize(Context context, Uri uri) {
try {
final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
long size = cursor.getLong(cursor.getColumnIndex(OpenableColumns.SIZE));
cursor.close();
return size;
} else {
return -1;
}
} catch (Exception e) {
return -1;
}
}
public static boolean allFilesUnderSize(Context context, List<Uri> uris, long max) {
if (max <= 0) {
Log.d(Config.LOGTAG, "server did not report max file size for http upload");
return true; //exception to be compatible with HTTP Upload < v0.2
}
for (Uri uri : uris) {
String mime = context.getContentResolver().getType(uri);
if (mime != null && mime.startsWith("video/")) {
try {
Dimensions dimensions = FileBackend.getVideoDimensions(context, uri);
if (dimensions.getMin() > 720) {
Log.d(Config.LOGTAG, "do not consider video file with min width larger than 720 for size check");
continue;
}
} catch (NotAVideoFile notAVideoFile) {
//ignore and fall through
}
}
if (FileBackend.getFileSize(context, uri) > max) {
Log.d(Config.LOGTAG, "not all files are under " + max + " bytes. suggesting falling back to jingle");
return false;
}
}
return true;
}
public static String getConversationsDirectory(Context context, final String type) {
if (Config.ONLY_INTERNAL_STORAGE) {
return context.getFilesDir().getAbsolutePath() + "/" + type + "/";
} else {
return getAppMediaDirectory(context) + context.getString(R.string.app_name) + " " + type + "/";
}
}
public static String getAppMediaDirectory(Context context) {
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + context.getString(R.string.app_name) + "/Media/";
}
public static String getConversationsLogsDirectory() {
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Conversations/";
}
private static Bitmap rotate(Bitmap bitmap, int degree) {
if (degree == 0) {
return bitmap;
}
int w = bitmap.getWidth();
int h = bitmap.getHeight();
Matrix mtx = new Matrix();
mtx.postRotate(degree);
Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
return result;
}
public static boolean isPathBlacklisted(String path) {
final String androidDataPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/";
return path.startsWith(androidDataPath);
}
private static Paint createAntiAliasingPaint() {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
paint.setDither(true);
return paint;
}
private static String getTakePhotoPath() {
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + "/Camera/";
}
public static Uri getUriForFile(Context context, File file) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N || Config.ONLY_INTERNAL_STORAGE) {
try {
return FileProvider.getUriForFile(context, getAuthority(context), file);
} catch (IllegalArgumentException e) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
throw new SecurityException(e);
} else {
return Uri.fromFile(file);
}
}
} else {
return Uri.fromFile(file);
}
}
public static String getAuthority(Context context) {
return context.getPackageName() + FILE_PROVIDER;
}
public static Uri getIndexableTakePhotoUri(Uri original) {
if (Config.ONLY_INTERNAL_STORAGE || "file".equals(original.getScheme())) {
return original;
} else {
List<String> segments = original.getPathSegments();
return Uri.parse("file://" + getTakePhotoPath() + segments.get(segments.size() - 1));
}
}
private static boolean hasAlpha(final Bitmap bitmap) {
for (int x = 0; x < bitmap.getWidth(); ++x) {
for (int y = 0; y < bitmap.getWidth(); ++y) {
if (Color.alpha(bitmap.getPixel(x, y)) < 255) {
return true;
}
}
}
return false;
}
private static int calcSampleSize(File image, int size) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(image.getAbsolutePath(), options);
return calcSampleSize(options, size);
}
public static int calcSampleSize(BitmapFactory.Options options, int size) {
int height = options.outHeight;
int width = options.outWidth;
int inSampleSize = 1;
if (height > size || width > size) {
int halfHeight = height / 2;
int halfWidth = width / 2;
while ((halfHeight / inSampleSize) > size
&& (halfWidth / inSampleSize) > size) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
private static Dimensions getVideoDimensions(Context context, Uri uri) throws NotAVideoFile {
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
try {
mediaMetadataRetriever.setDataSource(context, uri);
} catch (RuntimeException e) {
throw new NotAVideoFile(e);
}
return getVideoDimensions(mediaMetadataRetriever);
}
private static Dimensions getVideoDimensionsOfFrame(MediaMetadataRetriever mediaMetadataRetriever) {
Bitmap bitmap = null;
try {
bitmap = mediaMetadataRetriever.getFrameAtTime();
return new Dimensions(bitmap.getHeight(), bitmap.getWidth());
} catch (Exception e) {
return null;
} finally {
if (bitmap != null) {
bitmap.recycle();
;
}
}
}
private static Dimensions getVideoDimensions(MediaMetadataRetriever metadataRetriever) throws NotAVideoFile {
String hasVideo = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
if (hasVideo == null) {
throw new NotAVideoFile();
}
Dimensions dimensions = getVideoDimensionsOfFrame(metadataRetriever);
if (dimensions != null) {
return dimensions;
}
int rotation = extractRotationFromMediaRetriever(metadataRetriever);
boolean rotated = rotation == 90 || rotation == 270;
int height;
try {
String h = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
height = Integer.parseInt(h);
} catch (Exception e) {
height = -1;
}
int width;
try {
String w = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
width = Integer.parseInt(w);
} catch (Exception e) {
width = -1;
}
metadataRetriever.release();
Log.d(Config.LOGTAG, "extracted video dims " + width + "x" + height);
return rotated ? new Dimensions(width, height) : new Dimensions(height, width);
}
private static int extractRotationFromMediaRetriever(MediaMetadataRetriever metadataRetriever) {
String r = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
try {
return Integer.parseInt(r);
} catch (Exception e) {
return 0;
}
}
public static void close(Closeable stream) {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
}
}
}
public static void close(Socket socket) {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
}
}
}
public static boolean weOwnFile(Context context, Uri uri) {
if (uri == null || !ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return false;
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return fileIsInFilesDir(context, uri);
} else {
return weOwnFileLollipop(uri);
}
}
/**
* This is more than hacky but probably way better than doing nothing
* Further 'optimizations' might contain to get the parents of CacheDir and NoBackupDir
* and check against those as well
*/
private static boolean fileIsInFilesDir(Context context, Uri uri) {
try {
final String haystack = context.getFilesDir().getParentFile().getCanonicalPath();
final String needle = new File(uri.getPath()).getCanonicalPath();
return needle.startsWith(haystack);
} catch (IOException e) {
return false;
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static boolean weOwnFileLollipop(Uri uri) {
try {
File file = new File(uri.getPath());
FileDescriptor fd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY).getFileDescriptor();
StructStat st = Os.fstat(fd);
return st.st_uid == android.os.Process.myUid();
} catch (FileNotFoundException e) {
return false;
} catch (Exception e) {
return true;
}
}
private void createNoMedia(File diretory) {
final File noMedia = new File(diretory, ".nomedia");
if (!noMedia.exists()) {
@ -100,19 +387,6 @@ public class FileBackend {
}
}
private static boolean isInDirectoryThatShouldNotBeScanned(Context context, File file) {
return isInDirectoryThatShouldNotBeScanned(context, file.getAbsolutePath());
}
public static boolean isInDirectoryThatShouldNotBeScanned(Context context, String path) {
for(String type : new String[]{RecordingActivity.STORAGE_DIRECTORY_TYPE_NAME, "Files"}) {
if (path.startsWith(getConversationsDirectory(context, type))) {
return true;
}
}
return false;
}
public boolean deleteFile(Message message) {
File file = getFile(message);
if (file.delete()) {
@ -159,82 +433,27 @@ public class FileBackend {
}
}
public static long getFileSize(Context context, Uri uri) {
try {
final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
long size = cursor.getLong(cursor.getColumnIndex(OpenableColumns.SIZE));
cursor.close();
return size;
} else {
return -1;
}
} catch (Exception e) {
return -1;
}
}
public static boolean allFilesUnderSize(Context context, List<Uri> uris, long max) {
if (max <= 0) {
Log.d(Config.LOGTAG, "server did not report max file size for http upload");
return true; //exception to be compatible with HTTP Upload < v0.2
}
for (Uri uri : uris) {
String mime = context.getContentResolver().getType(uri);
if (mime != null && mime.startsWith("video/")) {
try {
Dimensions dimensions = FileBackend.getVideoDimensions(context, uri);
if (dimensions.getMin() > 720) {
Log.d(Config.LOGTAG, "do not consider video file with min width larger than 720 for size check");
continue;
}
} catch (NotAVideoFile notAVideoFile) {
//ignore and fall through
}
}
if (FileBackend.getFileSize(context, uri) > max) {
Log.d(Config.LOGTAG, "not all files are under " + max + " bytes. suggesting falling back to jingle");
return false;
}
}
return true;
}
public String getConversationsDirectory(final String type) {
return getConversationsDirectory(mXmppConnectionService, type);
}
public static String getConversationsDirectory(Context context, final String type) {
if (Config.ONLY_INTERNAL_STORAGE) {
return context.getFilesDir().getAbsolutePath() + "/" + type + "/";
} else {
return getAppMediaDirectory(context)+context.getString(R.string.app_name)+" " + type + "/";
}
}
public static String getAppMediaDirectory(Context context) {
return Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+context.getString(R.string.app_name)+"/Media/";
}
public static String getConversationsLogsDirectory() {
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Conversations/";
}
public Bitmap resize(Bitmap originalBitmap, int size) {
private Bitmap resize(final Bitmap originalBitmap, int size) throws IOException {
int w = originalBitmap.getWidth();
int h = originalBitmap.getHeight();
if (Math.max(w, h) > size) {
if (w <= 0 || h <= 0) {
throw new IOException("Decoded bitmap reported bounds smaller 0");
} else if (Math.max(w, h) > size) {
int scalledW;
int scalledH;
if (w <= h) {
scalledW = (int) (w / ((double) h / size));
scalledW = Math.max((int) (w / ((double) h / size)), 1);
scalledH = size;
} else {
scalledW = size;
scalledH = (int) (h / ((double) w / size));
scalledH = Math.max((int) (h / ((double) w / size)), 1);
}
Bitmap result = Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true);
if (originalBitmap != null && !originalBitmap.isRecycled()) {
final Bitmap result = Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true);
if (!originalBitmap.isRecycled()) {
originalBitmap.recycle();
}
return result;
@ -243,22 +462,6 @@ public class FileBackend {
}
}
private static Bitmap rotate(Bitmap bitmap, int degree) {
if (degree == 0) {
return bitmap;
}
int w = bitmap.getWidth();
int h = bitmap.getHeight();
Matrix mtx = new Matrix();
mtx.postRotate(degree);
Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
return result;
}
public boolean useImageAsIs(Uri uri) {
String path = getOriginalPath(uri);
if (path == null || isPathBlacklisted(path)) {
@ -282,11 +485,6 @@ public class FileBackend {
}
}
public static boolean isPathBlacklisted(String path) {
final String androidDataPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/";
return path.startsWith(androidDataPath);
}
public String getOriginalPath(Uri uri) {
return FileUtils.getPath(mXmppConnectionService, uri);
}
@ -463,7 +661,7 @@ public class FileBackend {
}
}
public Bitmap getThumbnail(Message message, int size, boolean cacheOnly) throws FileNotFoundException {
public Bitmap getThumbnail(Message message, int size, boolean cacheOnly) throws IOException {
final String uuid = message.getUuid();
final LruCache<String, Bitmap> cache = mXmppConnectionService.getBitmapCache();
Bitmap thumbnail = cache.get(uuid);
@ -519,14 +717,6 @@ public class FileBackend {
canvas.drawBitmap(overlay, null, dst, createAntiAliasingPaint());
}
private static Paint createAntiAliasingPaint() {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
paint.setDither(true);
return paint;
}
private Bitmap getVideoPreview(File file, int size) {
MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
Bitmap frame;
@ -535,7 +725,7 @@ public class FileBackend {
frame = metadataRetriever.getFrameAtTime(0);
metadataRetriever.release();
frame = resize(frame, size);
} catch (RuntimeException e) {
} catch (IOException | RuntimeException e) {
frame = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
frame.eraseColor(0xff000000);
}
@ -543,10 +733,6 @@ public class FileBackend {
return frame;
}
private static String getTakePhotoPath() {
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + "/Camera/";
}
public Uri getTakePhotoUri() {
File file;
if (Config.ONLY_INTERNAL_STORAGE) {
@ -558,35 +744,6 @@ public class FileBackend {
return getUriForFile(mXmppConnectionService, file);
}
public static Uri getUriForFile(Context context, File file) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N || Config.ONLY_INTERNAL_STORAGE) {
try {
return FileProvider.getUriForFile(context, getAuthority(context), file);
} catch (IllegalArgumentException e) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
throw new SecurityException(e);
} else {
return Uri.fromFile(file);
}
}
} else {
return Uri.fromFile(file);
}
}
public static String getAuthority(Context context) {
return context.getPackageName() + FILE_PROVIDER;
}
public static Uri getIndexableTakePhotoUri(Uri original) {
if (Config.ONLY_INTERNAL_STORAGE || "file".equals(original.getScheme())) {
return original;
} else {
List<String> segments = original.getPathSegments();
return Uri.parse("file://" + getTakePhotoPath() + segments.get(segments.size() - 1));
}
}
public Avatar getPepAvatar(Uri image, int size, Bitmap.CompressFormat format) {
Bitmap bm = cropCenterSquare(image, size);
if (bm == null) {
@ -601,17 +758,6 @@ public class FileBackend {
return getPepAvatar(bm, format, 100);
}
private static boolean hasAlpha(final Bitmap bitmap) {
for (int x = 0; x < bitmap.getWidth(); ++x) {
for (int y = 0; y < bitmap.getWidth(); ++y) {
if (Color.alpha(bitmap.getPixel(x, y)) < 255) {
return true;
}
}
}
return false;
}
private Avatar getPepAvatar(Bitmap bitmap, Bitmap.CompressFormat format, int quality) {
try {
ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();
@ -846,30 +992,6 @@ public class FileBackend {
return calcSampleSize(options, size);
}
private static int calcSampleSize(File image, int size) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(image.getAbsolutePath(), options);
return calcSampleSize(options, size);
}
public static int calcSampleSize(BitmapFactory.Options options, int size) {
int height = options.outHeight;
int width = options.outWidth;
int inSampleSize = 1;
if (height > size || width > size) {
int halfHeight = height / 2;
int halfWidth = width / 2;
while ((halfHeight / inSampleSize) > size
&& (halfWidth / inSampleSize) > size) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public void updateFileParams(Message message) {
updateFileParams(message, null);
}
@ -942,67 +1064,19 @@ public class FileBackend {
return getVideoDimensions(metadataRetriever);
}
private static Dimensions getVideoDimensions(Context context, Uri uri) throws NotAVideoFile {
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
try {
mediaMetadataRetriever.setDataSource(context, uri);
} catch (RuntimeException e) {
throw new NotAVideoFile(e);
}
return getVideoDimensions(mediaMetadataRetriever);
}
private static Dimensions getVideoDimensionsOfFrame(MediaMetadataRetriever mediaMetadataRetriever) {
Bitmap bitmap = null;
try {
bitmap = mediaMetadataRetriever.getFrameAtTime();
return new Dimensions(bitmap.getHeight(), bitmap.getWidth());
} catch (Exception e) {
public Bitmap getAvatar(String avatar, int size) {
if (avatar == null) {
return null;
} finally {
if (bitmap != null) {
bitmap.recycle();;
}
Bitmap bm = cropCenter(getAvatarUri(avatar), size, size);
if (bm == null) {
return null;
}
return bm;
}
private static Dimensions getVideoDimensions(MediaMetadataRetriever metadataRetriever) throws NotAVideoFile {
String hasVideo = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
if (hasVideo == null) {
throw new NotAVideoFile();
}
Dimensions dimensions = getVideoDimensionsOfFrame(metadataRetriever);
if (dimensions != null) {
return dimensions;
}
int rotation = extractRotationFromMediaRetriever(metadataRetriever);
boolean rotated = rotation == 90 || rotation == 270;
int height;
try {
String h = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
height = Integer.parseInt(h);
} catch (Exception e) {
height = -1;
}
int width;
try {
String w = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
width = Integer.parseInt(w);
} catch (Exception e) {
width = -1;
}
metadataRetriever.release();
Log.d(Config.LOGTAG, "extracted video dims " + width + "x" + height);
return rotated ? new Dimensions(width, height) : new Dimensions(height, width);
}
private static int extractRotationFromMediaRetriever(MediaMetadataRetriever metadataRetriever) {
String r = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
try {
return Integer.parseInt(r);
} catch (Exception e) {
return 0;
}
public boolean isFileAvailable(Message message) {
return getFile(message).exists();
}
private static class Dimensions {
@ -1045,78 +1119,4 @@ public class FileBackend {
return resId;
}
}
public Bitmap getAvatar(String avatar, int size) {
if (avatar == null) {
return null;
}
Bitmap bm = cropCenter(getAvatarUri(avatar), size, size);
if (bm == null) {
return null;
}
return bm;
}
public boolean isFileAvailable(Message message) {
return getFile(message).exists();
}
public static void close(Closeable stream) {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
}
}
}
public static void close(Socket socket) {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
}
}
}
public static boolean weOwnFile(Context context, Uri uri) {
if (uri == null || !ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return false;
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return fileIsInFilesDir(context, uri);
} else {
return weOwnFileLollipop(uri);
}
}
/**
* This is more than hacky but probably way better than doing nothing
* Further 'optimizations' might contain to get the parents of CacheDir and NoBackupDir
* and check against those as well
*/
private static boolean fileIsInFilesDir(Context context, Uri uri) {
try {
final String haystack = context.getFilesDir().getParentFile().getCanonicalPath();
final String needle = new File(uri.getPath()).getCanonicalPath();
return needle.startsWith(haystack);
} catch (IOException e) {
return false;
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static boolean weOwnFileLollipop(Uri uri) {
try {
File file = new File(uri.getPath());
FileDescriptor fd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY).getFileDescriptor();
StructStat st = Os.fstat(fd);
return st.st_uid == android.os.Process.myUid();
} catch (FileNotFoundException e) {
return false;
} catch (Exception e) {
return true;
}
}
}

View File

@ -26,6 +26,7 @@ import android.util.Pair;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
@ -504,7 +505,7 @@ public class NotificationService {
builder.setContentText(UIHelper.getFileDescriptionString(mXmppConnectionService, message));
}
builder.setStyle(bigPictureStyle);
} catch (final FileNotFoundException e) {
} catch (final IOException e) {
modifyForTextOnly(builder, uBuilder, messages);
}
}

View File

@ -48,6 +48,7 @@ import android.widget.ImageView;
import android.widget.Toast;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@ -876,7 +877,7 @@ public abstract class XmppActivity extends ActionBarActivity {
Bitmap bm;
try {
bm = xmppConnectionService.getFileBackend().getThumbnail(message, (int) (metrics.density * 288), true);
} catch (FileNotFoundException e) {
} catch (IOException e) {
bm = null;
}
if (bm != null) {
@ -970,7 +971,7 @@ public abstract class XmppActivity extends ActionBarActivity {
} else {
return null;
}
} catch (FileNotFoundException e) {
} catch (IOException e) {
return null;
}
}