From 5a670c88b081d3f9b8d33e37fd8d966f04c245fc Mon Sep 17 00:00:00 2001 From: Michael Eden Date: Fri, 30 Dec 2016 15:48:39 -0500 Subject: [PATCH] Do not compress GIFs, allow GBoard to send GIFs --- build.gradle | 2 +- .../persistance/FileBackend.java | 3 +- .../services/XmppConnectionService.java | 7 +++- .../ui/ConversationActivity.java | 4 ++ .../ui/ConversationFragment.java | 36 ++++++++++++++++++ .../siacs/conversations/ui/EditMessage.java | 37 +++++++++++++++++++ .../siacs/conversations/utils/MimeUtils.java | 21 +++++++++++ 7 files changed, 107 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 6fd725c9..fbc1cf04 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ dependencies { playstoreCompile 'com.google.android.gms:play-services-gcm:9.4.0' compile 'org.sufficientlysecure:openpgp-api:10.0' compile 'com.soundcloud.android:android-crop:1.0.1@aar' - compile 'com.android.support:support-v13:24.2.0' + compile 'com.android.support:support-v13:25.1.0' compile 'org.bouncycastle:bcprov-jdk15on:1.52' compile 'org.bouncycastle:bcmail-jdk15on:1.52' compile 'org.jitsi:org.otr4j:0.22' diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 84330d16..642df769 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -55,6 +55,7 @@ import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.ExifHelper; import eu.siacs.conversations.utils.FileUtils; import eu.siacs.conversations.utils.FileWriterException; +import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.xmpp.pep.Avatar; public class FileBackend { @@ -276,7 +277,7 @@ public class FileBackend { } public void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException { - String mime = mXmppConnectionService.getContentResolver().getType(uri); + String mime = MimeUtils.guessMimeTypeFromUri(mXmppConnectionService, uri); Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage (mime="+mime+")"); String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime); if (extension == null) { diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 38c1214a..5524241c 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -98,6 +98,7 @@ import eu.siacs.conversations.ui.UiCallback; import eu.siacs.conversations.utils.ConversationsFileObserver; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.ExceptionHelper; +import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener; import eu.siacs.conversations.utils.PRNGFixes; import eu.siacs.conversations.utils.PhoneHelper; @@ -493,9 +494,13 @@ public class XmppConnectionService extends Service { callback.error(R.string.security_error_invalid_file_access, null); return; } + + final String mimeType = MimeUtils.guessMimeTypeFromUri(this, uri); final String compressPictures = getCompressPicturesPreference(); + if ("never".equals(compressPictures) - || ("auto".equals(compressPictures) && getFileBackend().useImageAsIs(uri))) { + || ("auto".equals(compressPictures) && getFileBackend().useImageAsIs(uri)) + || (mimeType != null && mimeType.endsWith("/gif"))) { Log.d(Config.LOGTAG,conversation.getAccount().getJid().toBareJid()+ ": not compressing picture. sending as file"); attachFileToConversation(conversation, uri, callback); return; diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 8056139d..11fa76c2 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -1560,6 +1560,10 @@ public class ConversationActivity extends XmppActivity }); } + public void attachImageToConversation(Uri uri) { + this.attachImageToConversation(getSelectedConversation(), uri); + } + private void attachImageToConversation(Conversation conversation, Uri uri) { if (conversation == null) { return; diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 761f8054..27b12ae2 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -11,6 +11,8 @@ import android.content.Intent; import android.content.IntentSender.SendIntentException; import android.os.Bundle; import android.os.Handler; +import android.support.v13.view.inputmethod.InputConnectionCompat; +import android.support.v13.view.inputmethod.InputContentInfoCompat; import android.text.Editable; import android.text.InputType; import android.util.Log; @@ -285,6 +287,37 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } } }; + private EditMessage.OnCommitContentListener mEditorContentListener = new EditMessage.OnCommitContentListener() { + @Override + public boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts, String[] contentMimeTypes) { + // try to get permission to read the image, if applicable + if ((flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { + try { + inputContentInfo.requestPermission(); + } catch (Exception e) { + Log.e(Config.LOGTAG, "InputContentInfoCompat#requestPermission() failed.", e); + Toast.makeText( + activity, + activity.getString(R.string.no_permission_to_access_x, inputContentInfo.getDescription()), + Toast.LENGTH_LONG + ).show(); + return false; + } + } + + // send the image + activity.attachImageToConversation(inputContentInfo.getContentUri()); + + // TODO: revoke permissions? + // since uploading an image is async its tough to wire a callback to when + // the image has finished uploading. + // According to the docs: "calling IC#releasePermission() is just to be a + // good citizen. Even if we failed to call that method, the system would eventually revoke + // the permission sometime after inputContentInfo object gets garbage-collected." + // See: https://developer.android.com/samples/CommitContentSampleApp/src/com.example.android.commitcontent.app/MainActivity.html#l164 + return true; + } + }; private OnClickListener mSendButtonListener = new OnClickListener() { @Override @@ -416,6 +449,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.fragment_conversation, container, false); view.setOnClickListener(null); + + String[] allImagesMimeType = {"image/*"}; mEditMessage = (EditMessage) view.findViewById(R.id.textinput); mEditMessage.setOnClickListener(new OnClickListener() { @@ -427,6 +462,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } }); mEditMessage.setOnEditorActionListener(mEditorActionListener); + mEditMessage.setRichContentListener(allImagesMimeType, mEditorContentListener); mSendButton = (ImageButton) view.findViewById(R.id.textSendButton); mSendButton.setOnClickListener(this.mSendButtonListener); diff --git a/src/main/java/eu/siacs/conversations/ui/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/EditMessage.java index e609d08e..6686d951 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditMessage.java +++ b/src/main/java/eu/siacs/conversations/ui/EditMessage.java @@ -1,19 +1,33 @@ package eu.siacs.conversations.ui; +import android.support.v13.view.inputmethod.EditorInfoCompat; +import android.support.v13.view.inputmethod.InputConnectionCompat; +import android.support.v13.view.inputmethod.InputContentInfoCompat; + import android.content.Context; import android.os.Build; +import android.os.Bundle; import android.os.Handler; import android.text.Editable; import android.text.InputFilter; import android.text.Spanned; import android.util.AttributeSet; import android.view.KeyEvent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; import android.widget.EditText; import eu.siacs.conversations.Config; public class EditMessage extends EditText { + public interface OnCommitContentListener { + boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts, String[] mimeTypes); + } + + private OnCommitContentListener mCommitContentListener = null; + private String[] mimeTypes = null; + public EditMessage(Context context, AttributeSet attrs) { super(context, attrs); } @@ -125,4 +139,27 @@ public class EditMessage extends EditText { return super.onTextContextMenuItem(id); } } + + public void setRichContentListener(String[] mimeTypes, OnCommitContentListener listener) { + this.mimeTypes = mimeTypes; + this.mCommitContentListener = listener; + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo editorInfo) { + final InputConnection ic = super.onCreateInputConnection(editorInfo); + + if (mimeTypes != null && mCommitContentListener != null) { + EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes); + return InputConnectionCompat.createWrapper(ic, editorInfo, new InputConnectionCompat.OnCommitContentListener() { + @Override + public boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts) { + return EditMessage.this.mCommitContentListener.onCommitContent(inputContentInfo, flags, opts, mimeTypes); + } + }); + } + else { + return ic; + } + } } diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java index d4544424..b65eb73c 100644 --- a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java @@ -14,6 +14,9 @@ * limitations under the License. */ package eu.siacs.conversations.utils; +import android.content.Context; +import android.net.Uri; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -484,4 +487,22 @@ public final class MimeUtils { } return mimeTypeToExtensionMap.get(mimeType); } + + public static String guessMimeTypeFromUri(Context context, Uri uri) { + // try the content resolver + String mimeType = context.getContentResolver().getType(uri); + // try the extension + if (mimeType == null && uri.getPath() != null) { + String path = uri.getPath(); + int start = path.lastIndexOf('.') + 1; + if (start < path.length()) { + mimeType = MimeUtils.guessMimeTypeFromExtension(path.substring(start)); + } + } + // sometimes this works (as with the commit content api) + if (mimeType == null) { + mimeType = uri.getQueryParameter("mimeType"); + } + return mimeType; + } }