From 85779999c023c0188bd27bd3ec439650a4c5fb05 Mon Sep 17 00:00:00 2001 From: Christian Schneppe Date: Sun, 25 Dec 2016 18:24:50 +0100 Subject: catch NPE while sharing video from content uri and make it working --- .../messenger/services/XmppConnectionService.java | 351 +++++++++++++-------- .../java/de/pixart/messenger/utils/FileUtils.java | 27 +- 2 files changed, 242 insertions(+), 136 deletions(-) (limited to 'src/main') diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java index 8b7459b9e..56eafcc9a 100644 --- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java +++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java @@ -47,6 +47,10 @@ import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.math.BigInteger; import java.security.SecureRandom; import java.security.cert.CertificateException; @@ -107,6 +111,7 @@ import de.pixart.messenger.utils.ConversationsFileObserver; import de.pixart.messenger.utils.CryptoHelper; import de.pixart.messenger.utils.ExceptionHelper; import de.pixart.messenger.utils.FileUtils; +import de.pixart.messenger.utils.FileWriterException; import de.pixart.messenger.utils.OnPhoneContactsLoadedListener; import de.pixart.messenger.utils.PRNGFixes; import de.pixart.messenger.utils.PhoneHelper; @@ -140,6 +145,9 @@ import de.pixart.messenger.xmpp.stanzas.MessagePacket; import de.pixart.messenger.xmpp.stanzas.PresencePacket; import me.leolin.shortcutbadger.ShortcutBadger; +import static de.pixart.messenger.persistance.FileBackend.close; +import static de.pixart.messenger.services.NotificationService.NOTIFICATION_ID; + public class XmppConnectionService extends Service { public static final String ACTION_REPLY_TO_CONVERSATION = "reply_to_conversations"; @@ -148,23 +156,23 @@ public class XmppConnectionService extends Service { public static final String ACTION_TRY_AGAIN = "try_again"; public static final String ACTION_DISMISS_ERROR_NOTIFICATIONS = "dismiss_error"; public static final String ACTION_IDLE_PING = "idle_ping"; - private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts"; public static final String ACTION_GCM_TOKEN_REFRESH = "gcm_token_refresh"; public static final String ACTION_GCM_MESSAGE_RECEIVED = "gcm_message_received"; + private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts"; + public static VideoCompressor CompressVideo; private final SerialSingleThreadExecutor mFileAddingExecutor = new SerialSingleThreadExecutor(); private final SerialSingleThreadExecutor mDatabaseExecutor = new SerialSingleThreadExecutor(); - private ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor(true); + private final SerialSingleThreadExecutor mCopyVideoExecutor = new SerialSingleThreadExecutor(); private final IBinder mBinder = new XmppConnectionBinder(); private final List conversations = new CopyOnWriteArrayList<>(); private final IqGenerator mIqGenerator = new IqGenerator(this); private final List mInProgressAvatarFetches = new ArrayList<>(); private final HashSet mLowPingTimeoutMode = new HashSet<>(); + public DatabaseBackend databaseBackend; + private ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor(true); private WakeLock wakeLock; private long mLastActivity = 0; - public static VideoCompressor CompressVideo; private NotificationManager mNotifyManager; - - public DatabaseBackend databaseBackend; private ContentObserver contactObserver = new ContentObserver(null) { @Override public void onChange(boolean selfChange) { @@ -274,9 +282,6 @@ public class XmppConnectionService extends Service { private OnMucRosterUpdate mOnMucRosterUpdate = null; private int mucRosterChangedListenerCount = 0; private OnKeyStatusUpdated mOnKeyStatusUpdated = null; - private int keyStatusUpdatedListenerCount = 0; - private SecureRandom mRandom; - private LruCache, ServiceDiscoveryResult> discoCache = new LruCache<>(20); private final OnBindListener mOnBindListener = new OnBindListener() { @Override @@ -301,6 +306,9 @@ public class XmppConnectionService extends Service { syncDirtyContacts(account); } }; + private int keyStatusUpdatedListenerCount = 0; + private SecureRandom mRandom; + private LruCache, ServiceDiscoveryResult> discoCache = new LruCache<>(20); private OnStatusChanged statusListener = new OnStatusChanged() { @Override @@ -544,107 +552,137 @@ public class XmppConnectionService extends Service { } public void attachVideoToConversation(final Conversation conversation, final Uri uri, final UiCallback callback) { - File f = new File(FileUtils.getPath(this, uri)); - long filesize = f.length(); - String path = f.toString(); - final String compressVideos = getCompressVideoPreference(); - boolean sendVideoAsIs = false; final Integer NOTIFICATION_ID = (int) (new Date().getTime() / 1000); - mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext()); - mBuilder.setContentTitle(getString(R.string.app_name)) - .setContentText(getString(R.string.compressing_video)) - .setSmallIcon(R.drawable.ic_play_box_outline_white_24dp) - .setOngoing(true) - .setProgress(0, 0, true); - mNotifyManager.notify(NOTIFICATION_ID, mBuilder.build()); - if (FileBackend.weOwnFile(this, uri)) { - Log.d(Config.LOGTAG, "trying to attach video that belonged to us"); - mNotifyManager.cancel(NOTIFICATION_ID); - callback.error(R.string.security_error_invalid_file_access, null); - return; - } - Log.d(Config.LOGTAG, "Video file (size) :" + f.toString() + "(" + filesize / 1024 / 1024 + "MB)"); - if (filesize == 0) { - Log.d(Config.LOGTAG, "Error with file, size = 0"); - mNotifyManager.cancel(NOTIFICATION_ID); - callback.error(R.string.error_file_corrupt, null); - return; - } - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); - File compressed_file = new File(FileBackend.getConversationsVideoDirectory() + "/" - + dateFormat.format(new Date()) - + "_komp.mp4"); - final String compressed_path = compressed_file.toString(); - final Uri compressed_uri = Uri.fromFile(compressed_file); - if ("never".equals(compressVideos)) { - sendVideoAsIs = true; - } else if ("always".equals(compressVideos) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - sendVideoAsIs = false; - } else if ("auto".equals(compressVideos) && filesize > Config.VIDEO_MAX_SIZE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - sendVideoAsIs = false; - } else { - sendVideoAsIs = true; - } - if (!sendVideoAsIs) { - CompressVideo = new VideoCompressor(path, compressed_path, new Interface() { - @Override - public void videocompressed(boolean result) { - if (result) { - Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": sending compressed video."); - mNotifyManager.cancel(NOTIFICATION_ID); - attachFileToConversation(conversation, compressed_uri, callback); - } - } - }); - CompressVideo.execute(); - } else { - Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": not compressing video. sending as file"); - mNotifyManager.cancel(NOTIFICATION_ID); - attachFileToConversation(conversation, uri, callback); + final int[] success = {-1}; + File outputDir = this.getCacheDir(); + File file = null; + try { + file = File.createTempFile("temp-", "." + FileUtils.getExtension(this, uri), outputDir); + } catch (IOException e) { + Log.d(Config.LOGTAG, "error creating temp file"); } - } - - public interface Interface { - void videocompressed(boolean result); - } + long filesize = 0; + String path = null; + Boolean error = false; - class VideoCompressor extends AsyncTask { - private String originalpath; - private String compressedpath; - private Interface mListener; + final File finalFile = file; + new Thread(new Runnable() { - VideoCompressor(String path, String compressed_path, Interface mListener) { - originalpath = path; - compressedpath = compressed_path; - this.mListener = mListener; - } + @Override + public void run() { + try { + finalFile.getParentFile().mkdirs(); + Log.d(Config.LOGTAG, "copy video (" + uri.toString() + ") to temporarly storage " + finalFile.getAbsolutePath()); + OutputStream os = null; + InputStream is = null; + try { + finalFile.createNewFile(); + os = new FileOutputStream(finalFile); + is = XmppConnectionService.this.getContentResolver().openInputStream(uri); + byte[] buffer = new byte[1024]; + int length; + while ((length = is.read(buffer)) > 0) { + try { + os.write(buffer, 0, length); + } catch (IOException e) { + throw new FileWriterException(); + } + } + try { + os.flush(); + } catch (IOException e) { + throw new FileWriterException(); + } + } catch (Exception e) { + Log.d(Config.LOGTAG, "cannot copy video " + e); + success[0] = 2; + } finally { + success[0] = 1; + close(os); + close(is); + } + } catch (Exception e) { + Log.d(Config.LOGTAG, "cannot copy video " + e); + } + } + }).start(); - @Override - protected void onPreExecute() { - super.onPreExecute(); - Log.d(Config.LOGTAG, "Start video compression"); - wakeLock.acquire(); + while (success[0] == -1) { + Log.d(Config.LOGTAG, "Wait while copying video"); } - - @Override - protected Boolean doInBackground(String... params) { - return MediaController.getInstance().convertVideo(originalpath, compressedpath); + if (success[0] == 2) { + mNotifyManager.cancel(NOTIFICATION_ID); + callback.error(R.string.error_file_corrupt, null); + return; } - - @Override - protected void onPostExecute(Boolean compressed) { - super.onPostExecute(compressed); - wakeLock.release(); - File video = new File(compressedpath); - if (mListener != null) { - if (video.exists() && video.length() > 0) { - mListener.videocompressed(compressed); - Log.d(Config.LOGTAG, "Compression successfully!"); - } else { - mListener.videocompressed(false); - Log.d(Config.LOGTAG, "Compression failed!"); - } + if (success[0] == 1) { + try { + filesize = file.length(); + path = file.toString(); + } catch (Exception e) { + error = true; + Log.d(Config.LOGTAG, "Error retrieving file path " + e); + } + final String compressVideos = getCompressVideoPreference(); + boolean sendVideoAsIs = false; + mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext()); + mBuilder.setContentTitle(getString(R.string.app_name)) + .setContentText(getString(R.string.compressing_video)) + .setSmallIcon(R.drawable.ic_play_box_outline_white_24dp) + .setOngoing(true) + .setProgress(0, 0, true); + mNotifyManager.notify(NOTIFICATION_ID, mBuilder.build()); + if (FileBackend.weOwnFile(this, uri)) { + Log.d(Config.LOGTAG, "trying to attach video that belonged to us"); + mNotifyManager.cancel(NOTIFICATION_ID); + callback.error(R.string.security_error_invalid_file_access, null); + return; + } + if (file == null) { + error = true; + } + if (filesize == 0) { + error = true; + } + if (error) { + Log.d(Config.LOGTAG, "Error with file, size = 0"); + mNotifyManager.cancel(NOTIFICATION_ID); + callback.error(R.string.error_file_corrupt, null); + return; + } + Log.d(Config.LOGTAG, "Video file (size) :" + file.toString() + "(" + filesize / 1024 / 1024 + "MB)"); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); + File compressed_file = new File(FileBackend.getConversationsVideoDirectory() + "/" + + dateFormat.format(new Date()) + + "_komp.mp4"); + final String compressed_path = compressed_file.toString(); + final Uri compressed_uri = Uri.fromFile(compressed_file); + if ("never".equals(compressVideos)) { + sendVideoAsIs = true; + } else if ("always".equals(compressVideos) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + sendVideoAsIs = false; + } else if ("auto".equals(compressVideos) && filesize > Config.VIDEO_MAX_SIZE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + sendVideoAsIs = false; + } else { + sendVideoAsIs = true; + } + if (!sendVideoAsIs) { + CompressVideo = new VideoCompressor(path, compressed_path, new Interface() { + @Override + public void videocompressed(boolean result) { + if (result) { + Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": sending compressed video."); + mNotifyManager.cancel(NOTIFICATION_ID); + attachFileToConversation(conversation, compressed_uri, callback); + } + } + }); + CompressVideo.execute(); + } else { + Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": not compressing video. sending as file"); + mNotifyManager.cancel(NOTIFICATION_ID); + attachFileToConversation(conversation, uri, callback); } } } @@ -813,14 +851,14 @@ public class XmppConnectionService extends Service { long discoTimeout = Config.CONNECT_DISCO_TIMEOUT - secondsSinceLastDisco; long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect; if (timeout < 0) { - Log.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting (secondsSinceLast="+secondsSinceLastConnect+")"); + Log.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting (secondsSinceLast=" + secondsSinceLastConnect + ")"); account.getXmppConnection().resetAttemptCount(false); reconnectAccount(account, true, interactive); } else if (discoTimeout < 0) { account.getXmppConnection().sendDiscoTimeout(); - scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode()); + scheduleWakeUpCall((int) Math.min(timeout, discoTimeout), account.getUuid().hashCode()); } else { - scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode()); + scheduleWakeUpCall((int) Math.min(timeout, discoTimeout), account.getUuid().hashCode()); } } else { if (account.getXmppConnection().getTimeToNextAttempt() <= 0) { @@ -3211,7 +3249,6 @@ public class XmppConnectionService extends Service { vibrator.vibrate(100); } - public void showErrorToastInUi(int resId) { if (mOnShowErrorToast != null) { mOnShowErrorToast.onShowErrorToast(resId); @@ -3828,18 +3865,48 @@ public class XmppConnectionService extends Service { return getPreferences().getBoolean(SettingsActivity.BLIND_TRUST_BEFORE_VERIFICATION, true); } - public interface OnMamPreferencesFetched { - void onPreferencesFetched(Element prefs); - - void onPreferencesFetchFailed(); - } - public void pushMamPreferences(Account account, Element prefs) { IqPacket set = new IqPacket(IqPacket.TYPE.SET); set.addChild(prefs); sendIqPacket(account, set, null); } + public void ScheduleAutomaticExport() { + //start export log service every day at given time + if (Config.ExportLogs) { + if (Config.ExportLogs_Hour >= 0 && Config.ExportLogs_Hour <= 23 && Config.ExportLogs_Minute >= 0 && Config.ExportLogs_Minute <= 59) { + Log.d(Config.LOGTAG, "Schedule automatic export logs at " + Config.ExportLogs_Hour + ":" + Config.ExportLogs_Minute); + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(System.currentTimeMillis()); + calendar.set(Calendar.HOUR_OF_DAY, Config.ExportLogs_Hour); + calendar.set(Calendar.MINUTE, Config.ExportLogs_Minute); + Intent intent = new Intent(this, AlarmReceiver.class); + intent.setAction("exportlogs"); + final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, AlarmReceiver.SCHEDULE_ALARM_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT); + ((AlarmManager) getSystemService(ALARM_SERVICE)).setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pendingIntent); + } + } + } + + public void CancelAutomaticExport() { + if (Config.ExportLogs) { + Log.d(Config.LOGTAG, "Cancel scheduled automatic export"); + Intent intent = new Intent(this, AlarmReceiver.class); + final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, AlarmReceiver.SCHEDULE_ALARM_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT); + ((AlarmManager) this.getSystemService(ALARM_SERVICE)).cancel(pendingIntent); + } + } + + public interface Interface { + void videocompressed(boolean result); + } + + public interface OnMamPreferencesFetched { + void onPreferencesFetched(Element prefs); + + void onPreferencesFetchFailed(); + } + public interface OnAccountCreated { void onAccountCreated(Account account); @@ -3913,35 +3980,49 @@ public class XmppConnectionService extends Service { void onShowErrorToast(int resId); } - public class XmppConnectionBinder extends Binder { - public XmppConnectionService getService() { - return XmppConnectionService.this; + class VideoCompressor extends AsyncTask { + private String originalpath; + private String compressedpath; + private Interface mListener; + + VideoCompressor(String path, String compressed_path, Interface mListener) { + originalpath = path; + compressedpath = compressed_path; + this.mListener = mListener; } - } - public void ScheduleAutomaticExport() { - //start export log service every day at given time - if (Config.ExportLogs) { - if (Config.ExportLogs_Hour >= 0 && Config.ExportLogs_Hour <= 23 && Config.ExportLogs_Minute >= 0 && Config.ExportLogs_Minute <= 59) { - Log.d(Config.LOGTAG, "Schedule automatic export logs at " + Config.ExportLogs_Hour + ":" + Config.ExportLogs_Minute); - Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(System.currentTimeMillis()); - calendar.set(Calendar.HOUR_OF_DAY, Config.ExportLogs_Hour); - calendar.set(Calendar.MINUTE, Config.ExportLogs_Minute); - Intent intent = new Intent(this, AlarmReceiver.class); - intent.setAction("exportlogs"); - final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, AlarmReceiver.SCHEDULE_ALARM_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT); - ((AlarmManager) getSystemService(ALARM_SERVICE)).setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pendingIntent); + @Override + protected void onPreExecute() { + super.onPreExecute(); + Log.d(Config.LOGTAG, "Start video compression"); + wakeLock.acquire(); + } + + @Override + protected Boolean doInBackground(String... params) { + return MediaController.getInstance().convertVideo(originalpath, compressedpath); + } + + @Override + protected void onPostExecute(Boolean compressed) { + super.onPostExecute(compressed); + wakeLock.release(); + File video = new File(compressedpath); + if (mListener != null) { + if (video.exists() && video.length() > 0) { + mListener.videocompressed(compressed); + Log.d(Config.LOGTAG, "Compression successfully!"); + } else { + mListener.videocompressed(false); + Log.d(Config.LOGTAG, "Compression failed!"); + } } } } - public void CancelAutomaticExport() { - if (Config.ExportLogs) { - Log.d(Config.LOGTAG, "Cancel scheduled automatic export"); - Intent intent = new Intent(this, AlarmReceiver.class); - final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, AlarmReceiver.SCHEDULE_ALARM_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT); - ((AlarmManager) this.getSystemService(ALARM_SERVICE)).cancel(pendingIntent); + public class XmppConnectionBinder extends Binder { + public XmppConnectionService getService() { + return XmppConnectionService.this; } } } diff --git a/src/main/java/de/pixart/messenger/utils/FileUtils.java b/src/main/java/de/pixart/messenger/utils/FileUtils.java index 1500b6246..83d0e9fbc 100644 --- a/src/main/java/de/pixart/messenger/utils/FileUtils.java +++ b/src/main/java/de/pixart/messenger/utils/FileUtils.java @@ -1,6 +1,7 @@ package de.pixart.messenger.utils; import android.annotation.SuppressLint; +import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.database.Cursor; @@ -9,9 +10,13 @@ import android.os.Build; import android.os.Environment; import android.provider.DocumentsContract; import android.provider.MediaStore; +import android.util.Log; +import android.webkit.MimeTypeMap; import java.io.File; +import de.pixart.messenger.Config; + public class FileUtils { /** @@ -33,8 +38,10 @@ public class FileUtils { // DocumentProvider if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + Log.d(Config.LOGTAG, "FileUtils is KitKat"); // ExternalStorageProvider if (isExternalStorageDocument(uri)) { + Log.d(Config.LOGTAG, "FileUtils is external storage document"); final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; @@ -47,7 +54,7 @@ public class FileUtils { } // DownloadsProvider else if (isDownloadsDocument(uri)) { - + Log.d(Config.LOGTAG, "FileUtils is downloads document"); final String id = DocumentsContract.getDocumentId(uri); final Uri contentUri = ContentUris.withAppendedId( Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); @@ -56,6 +63,7 @@ public class FileUtils { } // MediaProvider else if (isMediaDocument(uri)) { + Log.d(Config.LOGTAG, "FileUtils is media provider"); final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; @@ -79,6 +87,7 @@ public class FileUtils { } // MediaStore (and general) else if ("content".equalsIgnoreCase(uri.getScheme())) { + Log.d(Config.LOGTAG, "FileUtils is media store"); String path = getDataColumn(context, uri, null, null); if (path != null) { File file = new File(path); @@ -90,6 +99,7 @@ public class FileUtils { } // File else if ("file".equalsIgnoreCase(uri.getScheme())) { + Log.d(Config.LOGTAG, "FileUtils is file"); return uri.getPath(); } @@ -155,4 +165,19 @@ public class FileUtils { public static boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); } + + public static String getExtension(Context context, Uri uri) { + String extension; + //Check uri format to avoid null + if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { + //If scheme is a content + final MimeTypeMap mime = MimeTypeMap.getSingleton(); + extension = mime.getExtensionFromMimeType(context.getContentResolver().getType(uri)); + } else { + //If scheme is a File + //This will replace white spaces with %20 and also other special characters. This will avoid returning null values on file name with spaces and special characters. + extension = MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(new File(uri.getPath())).toString()); + } + return extension; + } } -- cgit v1.2.3