diff --git a/app/src/main/java/helium314/keyboard/compat/EditorInfoCompatUtils.kt b/app/src/main/java/helium314/keyboard/compat/EditorInfoCompatUtils.kt index 7fa04b94e3..d501215441 100644 --- a/app/src/main/java/helium314/keyboard/compat/EditorInfoCompatUtils.kt +++ b/app/src/main/java/helium314/keyboard/compat/EditorInfoCompatUtils.kt @@ -9,6 +9,7 @@ package helium314.keyboard.compat import android.os.Build import android.text.InputType import android.view.inputmethod.EditorInfo +import androidx.core.view.inputmethod.EditorInfoCompat import helium314.keyboard.latin.utils.Log import java.util.* @@ -44,6 +45,16 @@ object EditorInfoCompatUtils { Log.d(tag, ("All caps: $allCaps, sentence caps: $sentenceCaps, word caps: $wordCaps")) } + @JvmStatic + fun isMimeTypeSupportedByEditor(editorInfo: EditorInfo?, mimeType: String): Boolean { + if (editorInfo == null) return false + val supportedMimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo) + return supportedMimeTypes.any { supported -> + supported == "*/*" || supported == mimeType + || (supported.endsWith("/*") && mimeType.startsWith(supported.removeSuffix("*"))) + } + } + @JvmStatic fun getHintLocales(editorInfo: EditorInfo?): List { if (editorInfo == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { diff --git a/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt b/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt index 3455e90846..4f832c5ac4 100644 --- a/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt +++ b/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt @@ -4,6 +4,7 @@ package helium314.keyboard.latin import android.content.ClipboardManager import android.content.Context +import android.net.Uri import android.text.InputType import android.text.TextUtils import android.view.LayoutInflater @@ -101,6 +102,12 @@ class ClipboardHistoryManager( return clipData.getItemAt(0)?.coerceToText(latinIME) ?: "" } + fun retrieveClipboardUri(): Uri? { + val clipData = clipboardManager.primaryClip ?: return null + if (clipData.itemCount == 0) return null + return clipData.getItemAt(0)?.uri + } + private fun isClipSensitive(inputType: Int): Boolean { ClipboardManagerCompat.getClipSensitivity(clipboardManager.primaryClip?.description)?.let { return it } return InputTypeUtils.isPasswordInputType(inputType) @@ -116,16 +123,30 @@ class ClipboardHistoryManager( if (dontShowCurrentSuggestion) return null if (parent == null) return null val clipData = clipboardManager.primaryClip ?: return null - if (clipData.itemCount == 0 || clipData.description?.hasMimeType("text/*") == false) return null + if (clipData.itemCount == 0) return null + val description = clipData.description + val hasText = description?.hasMimeType("text/*") == true + val hasImage = description?.hasMimeType("image/*") == true + if (!hasText && !hasImage) return null val clipItem = clipData.getItemAt(0) ?: return null val timeStamp = ClipboardManagerCompat.getClipTimestamp(clipData) if (System.currentTimeMillis() - timeStamp > RECENT_TIME_MILLIS) return null - val content = clipItem.coerceToText(latinIME) - if (TextUtils.isEmpty(content)) return null - val inputType = editorInfo?.inputType ?: InputType.TYPE_NULL - if (InputTypeUtils.isNumberInputType(inputType) && !content.isValidNumber()) return null - // create the view + val imageUri = if (hasImage) clipItem.uri else null + + if (imageUri == null) { + val content = clipItem.coerceToText(latinIME) + if (TextUtils.isEmpty(content)) return null + val inputType = editorInfo?.inputType ?: InputType.TYPE_NULL + if (InputTypeUtils.isNumberInputType(inputType) && !content.isValidNumber()) return null + return createTextSuggestionView(content, editorInfo, parent) + } + + return createImageSuggestionView(imageUri, parent) + } + + private fun createTextSuggestionView(content: CharSequence, editorInfo: EditorInfo?, parent: ViewGroup): View { + val inputType = editorInfo?.inputType ?: InputType.TYPE_NULL val binding = ClipboardSuggestionBinding.inflate(LayoutInflater.from(latinIME), parent, false) val textView = binding.clipboardSuggestionText latinIME.mSettings.getCustomTypeface()?.let { textView.typeface = it } @@ -139,18 +160,38 @@ class ClipboardHistoryManager( AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(KeyCode.NOT_SPECIFIED, it, HapticEvent.KEY_PRESS) binding.root.isGone = true } + setupCloseButtonAndColors(binding, clipIcon) + clipboardSuggestionView = binding.root + return binding.root + } + + private fun createImageSuggestionView(uri: Uri, parent: ViewGroup): View { + val binding = ClipboardSuggestionBinding.inflate(LayoutInflater.from(latinIME), parent, false) + binding.clipboardSuggestionText.isGone = true + val imageView = binding.clipboardSuggestionImage + imageView.visibility = View.VISIBLE + imageView.setImageURI(uri) + imageView.setOnClickListener { + dontShowCurrentSuggestion = true + latinIME.onUriInput(uri) + AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(KeyCode.NOT_SPECIFIED, it, HapticEvent.KEY_PRESS) + binding.root.isGone = true + } + setupCloseButtonAndColors(binding) + clipboardSuggestionView = binding.root + return binding.root + } + + private fun setupCloseButtonAndColors(binding: ClipboardSuggestionBinding, clipIcon: android.graphics.drawable.Drawable? = null) { val closeButton = binding.clipboardSuggestionClose closeButton.setImageDrawable(latinIME.mKeyboardSwitcher.keyboard.mIconsSet.getIconDrawable(ToolbarKey.CLOSE_HISTORY.name.lowercase())) closeButton.setOnClickListener { removeClipboardSuggestion() } val colors = latinIME.mSettings.current.mColors - textView.setTextColor(colors.get(ColorType.KEY_TEXT)) + binding.clipboardSuggestionText.setTextColor(colors.get(ColorType.KEY_TEXT)) clipIcon?.let { colors.setColor(it, ColorType.KEY_ICON) } colors.setColor(closeButton, ColorType.REMOVE_SUGGESTION_ICON) colors.setBackground(binding.root, ColorType.CLIPBOARD_SUGGESTION_BACKGROUND) - - clipboardSuggestionView = binding.root - return clipboardSuggestionView } private fun removeClipboardSuggestion() { diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index fe5458ab61..820496e931 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -9,6 +9,7 @@ import android.annotation.SuppressLint; import android.app.AlertDialog; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -17,6 +18,7 @@ import android.graphics.Color; import android.inputmethodservice.InputMethodService; import android.media.AudioManager; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Debug; @@ -1389,6 +1391,25 @@ public void onTextInput(final String rawText) { mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState()); } + public void onUriInput(final Uri uri) { + final EditorInfo editorInfo = getCurrentInputEditorInfo(); + if (editorInfo == null) return; + + String mimeType = getContentResolver().getType(uri); + if (mimeType == null) { + // ContentResolver may fail for some providers (e.g. Samsung clipboard). + // Fall back to the MIME type declared in the system clipboard's ClipDescription. + final android.content.ClipData clipData = ((android.content.ClipboardManager) + getSystemService(Context.CLIPBOARD_SERVICE)).getPrimaryClip(); + if (clipData != null && clipData.getDescription().getMimeTypeCount() > 0) { + mimeType = clipData.getDescription().getMimeType(0); + } + } + if (mimeType == null) mimeType = "application/octet-stream"; + + mInputLogic.mConnection.commitContent(uri, mimeType, editorInfo); + } + public void onStartBatchInput() { mInputLogic.onStartBatchInput(mSettings.getCurrent(), mKeyboardSwitcher, mHandler); mGestureConsumer.onGestureStarted(mRichImm.getCurrentSubtypeLocale(), mKeyboardSwitcher.getKeyboard()); diff --git a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java index 5e36225c4e..6616c88c81 100644 --- a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java +++ b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java @@ -10,6 +10,7 @@ import android.content.ClipboardManager; import android.content.Context; import android.inputmethodservice.InputMethodService; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.SystemClock; @@ -24,6 +25,7 @@ import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; @@ -31,6 +33,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.view.inputmethod.InputConnectionCompat; +import androidx.core.view.inputmethod.InputContentInfoCompat; import helium314.keyboard.latin.common.Constants; import helium314.keyboard.latin.common.StringUtils; @@ -1176,4 +1180,17 @@ public boolean requestCursorUpdates(final boolean enableMonitor, final boolean r | (requestImmediateCallback ? InputConnection.CURSOR_UPDATE_IMMEDIATE : 0); return mIC.requestCursorUpdates(cursorUpdateMode); } + + public boolean commitContent(@NonNull final Uri uri, @NonNull final String mimeType, + @NonNull final EditorInfo editorInfo) { + mIC = mParent.getCurrentInputConnection(); + if (!isConnected()) { + return false; + } + final InputContentInfoCompat contentInfo = new InputContentInfoCompat(uri, + new android.content.ClipDescription("clipboard image", new String[]{mimeType}), + null); + return InputConnectionCompat.commitContent(mIC, editorInfo, contentInfo, + InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION, null); + } } diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index afa7162e66..41c7b47c16 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -651,6 +651,11 @@ private void handleConsumedEvent(final Event event, final InputTransaction input * */ private void handleClipboardPaste() { + final android.net.Uri clipUri = mLatinIME.getClipboardHistoryManager().retrieveClipboardUri(); + if (clipUri != null) { + mLatinIME.onUriInput(clipUri); + return; + } final String clipboardContent = mLatinIME.getClipboardHistoryManager().retrieveClipboardContent().toString(); if (!clipboardContent.isEmpty()) { mLatinIME.onTextInput(clipboardContent); diff --git a/app/src/main/res/layout/clipboard_suggestion.xml b/app/src/main/res/layout/clipboard_suggestion.xml index 76125dcf2e..77c7e8fd62 100644 --- a/app/src/main/res/layout/clipboard_suggestion.xml +++ b/app/src/main/res/layout/clipboard_suggestion.xml @@ -7,6 +7,7 @@ android:paddingEnd="12dp" android:layout_width="wrap_content" android:layout_height="match_parent" + android:clipToOutline="true" tools:ignore="RtlSymmetry"> + Hide the toolbar when suggestions become available Content copied + + This app does not support pasting this content type Customize icons