diff --git a/app/src/main/java/helium314/keyboard/compat/EditorInfoCompatUtils.kt b/app/src/main/java/helium314/keyboard/compat/EditorInfoCompatUtils.kt index ac631bb8e..7cfdc5987 100644 --- a/app/src/main/java/helium314/keyboard/compat/EditorInfoCompatUtils.kt +++ b/app/src/main/java/helium314/keyboard/compat/EditorInfoCompatUtils.kt @@ -6,8 +6,10 @@ package helium314.keyboard.compat +import android.content.ClipDescription import android.os.Build import android.view.inputmethod.EditorInfo +import androidx.core.view.inputmethod.EditorInfoCompat import java.util.* import kotlin.collections.ArrayList @@ -40,4 +42,15 @@ object EditorInfoCompatUtils { } return locales } + + @JvmStatic + fun isMimeTypeSupportedByEditor(editorInfo: EditorInfo, mimeType: String): Boolean { + val editorMimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo) + for (supportedType in editorMimeTypes) { + if (ClipDescription.compareMimeTypes(mimeType, supportedType)) { + return true + } + } + return false + } } \ No newline at end of file diff --git a/app/src/main/java/helium314/keyboard/keyboard/clipboard/ClipboardAdapter.kt b/app/src/main/java/helium314/keyboard/keyboard/clipboard/ClipboardAdapter.kt index 4ba302621..4af089a49 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/clipboard/ClipboardAdapter.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/clipboard/ClipboardAdapter.kt @@ -50,7 +50,7 @@ class ClipboardAdapter( ) : RecyclerView.ViewHolder(view), View.OnClickListener, View.OnTouchListener, View.OnLongClickListener { private val pinnedIconView: ImageView - private val contentView: TextView + private val textView: TextView private val imageView: ImageView init { @@ -65,7 +65,7 @@ class ClipboardAdapter( visibility = View.GONE setImageResource(pinnedIconResId) } - contentView = view.findViewById(R.id.clipboard_entry_content).apply { + textView = view.findViewById(R.id.clipboard_entry_text).apply { typeface = itemTypeFace setTextColor(itemTextColor) setTextSize(TypedValue.COMPLEX_UNIT_PX, itemTextSize) @@ -79,13 +79,13 @@ class ClipboardAdapter( fun setContent(historyEntry: ClipboardHistoryEntry?) { itemView.tag = historyEntry?.timeStamp if (historyEntry?.imageUri != null) { - contentView.visibility = View.GONE + textView.visibility = View.GONE imageView.setImageURI(historyEntry.imageUri) imageView.visibility = View.VISIBLE } else { imageView.visibility = View.GONE - contentView.text = historyEntry?.content - contentView.visibility = View.VISIBLE + textView.text = historyEntry?.text + textView.visibility = View.VISIBLE } pinnedIconView.visibility = if (historyEntry?.isPinned == true) View.VISIBLE else View.GONE } diff --git a/app/src/main/java/helium314/keyboard/keyboard/clipboard/ClipboardHistoryView.kt b/app/src/main/java/helium314/keyboard/keyboard/clipboard/ClipboardHistoryView.kt index f1d30826e..f3096e738 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/clipboard/ClipboardHistoryView.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/clipboard/ClipboardHistoryView.kt @@ -269,7 +269,7 @@ class ClipboardHistoryView @JvmOverloads constructor( if (clipContent?.imageUri != null) { keyboardActionListener?.onUriInput(clipContent.imageUri) } else { - keyboardActionListener?.onTextInput(clipContent?.content.toString()) + keyboardActionListener?.onTextInput(clipContent?.text.toString()) } keyboardActionListener?.onReleaseKey(KeyCode.NOT_SPECIFIED, false) } diff --git a/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryEntry.kt b/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryEntry.kt index 62e6d0209..1a15c5e17 100644 --- a/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryEntry.kt +++ b/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryEntry.kt @@ -14,7 +14,7 @@ import kotlinx.serialization.encoding.Encoder data class ClipboardHistoryEntry ( var timeStamp: Long, @Serializable(with = CharSequenceStringSerializer::class) - val content: CharSequence, + val text: CharSequence, @Serializable(with = UriSerializer::class) val imageUri: Uri?, var isPinned: Boolean = false diff --git a/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt b/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt index 3568237e8..4b50c3084 100644 --- a/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt +++ b/app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt @@ -45,28 +45,23 @@ class ClipboardHistoryManager( private fun fetchPrimaryClip() { val clipData = clipboardManager.primaryClip ?: return if (clipData.itemCount == 0) return - val isImage = clipData.description?.hasMimeType("image/*") == true - val isText = !isImage && clipData.description?.hasMimeType("text/*") == true - if (!isText && !isImage) return clipData.getItemAt(0)?.let { clipItem -> val timeStamp = ClipboardManagerCompat.getClipTimestamp(clipData)?.also { stamp -> if (historyEntries.any { it.timeStamp == stamp }) return // nothing to change (may occur frequently starting with API 30) } ?: System.currentTimeMillis() val imageUri: Uri? - val content: CharSequence - if (isText) { - imageUri = null - content = clipItem.coerceToText(latinIME) - if (TextUtils.isEmpty(content)) return - - val duplicateEntryIndex = historyEntries.indexOfFirst { it.content.toString() == content.toString() } - if (updateTimestamp(duplicateEntryIndex, timeStamp)) return // older entry with the same text already exists - } else { + val text: CharSequence + if (clipData.description?.hasMimeType("image/*") == true) { imageUri = clipboardImageManager.saveClipboardImage(clipItem.uri, timeStamp) ?: return - content = clipData.description.label + text = "" + } else { + imageUri = null + text = clipItem.text ?: return + if (TextUtils.isEmpty(text)) return + if (updateTimestamp(text.toString(), timeStamp)) return // older entry with the same text already exists } - val entry = ClipboardHistoryEntry(timeStamp, content, imageUri) + val entry = ClipboardHistoryEntry(timeStamp, text, imageUri) historyEntries.add(entry) sortHistoryEntries() val at = historyEntries.indexOf(entry) @@ -74,17 +69,15 @@ class ClipboardHistoryManager( } } - // update the timestamp and re-sort the list - // return true if updated the timestamp, false otherwise - private fun updateTimestamp(index: Int, timeStamp: Long): Boolean { - if (index !in historyEntries.indices) return false - val existingEntry = historyEntries[index] - existingEntry.timeStamp = timeStamp - historyEntries.removeAt(index) - historyEntries.add(0, existingEntry) + // If an older entry with the specified text already exists, update its timestamp and re-sort the list. + // Returns true if the timestamp has been updated, false otherwise. + private fun updateTimestamp(text: String, newTimeStamp: Long): Boolean { + val oldEntry = historyEntries.firstOrNull { it.text.toString() == text } ?: return false + val oldIndex = historyEntries.indexOf(oldEntry) + oldEntry.timeStamp = newTimeStamp sortHistoryEntries() - val newIndex = historyEntries.indexOf(existingEntry) - onHistoryChangeListener?.onClipboardHistoryEntryMoved(index, newIndex) + val newIndex = historyEntries.indexOf(oldEntry) + onHistoryChangeListener?.onClipboardHistoryEntryMoved(oldIndex, newIndex) return true } @@ -150,10 +143,10 @@ class ClipboardHistoryManager( onHistoryChangeListener = l } - fun retrieveClipboardContent(): CharSequence { + fun retrieveClipboardText(): CharSequence { val clipData = clipboardManager.primaryClip ?: return "" if (clipData.itemCount == 0) return "" - return clipData.getItemAt(0)?.coerceToText(latinIME) ?: "" + return clipData.getItemAt(0)?.text ?: "" } fun retrieveClipboardUri(): Uri? { @@ -162,17 +155,6 @@ class ClipboardHistoryManager( return clipData.getItemAt(0)?.uri ?: return null } - fun pasteClipboard() { - if (clipboardManager.primaryClip?.description?.hasMimeType("text/*") == true) { - val text = retrieveClipboardContent() - if (!TextUtils.isEmpty(text)) - latinIME.onTextInput(text.toString()) - } else { - val uri = retrieveClipboardUri() ?: return - latinIME.onUriInput(uri) - } - } - // pinned clips are stored in default shared preferences, not in device protected preferences! private fun loadPinnedClips() { val pinnedClipString = Settings.readPinnedClipString(latinIME) diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index 4be41a3eb..0612e20bf 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -8,7 +8,6 @@ import android.annotation.SuppressLint; import android.content.BroadcastReceiver; -import android.content.ClipDescription; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -103,9 +102,6 @@ import androidx.annotation.RequiresApi; import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; -import androidx.core.view.inputmethod.EditorInfoCompat; -import androidx.core.view.inputmethod.InputConnectionCompat; -import androidx.core.view.inputmethod.InputContentInfoCompat; /** * Input method implementation for Qwerty'ish keyboard. @@ -1534,24 +1530,21 @@ public void onUriInput(@NonNull final Uri uri) { final String uriType = getContentResolver().getType(uri); final EditorInfo editorInfo = getCurrentInputEditorInfo(); if (editorInfo == null || uriType == null) return; - final String[] editorMimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo); - // Check if the editor supports the MIME type of the URI - for (String supportedType : editorMimeTypes) { - if (ClipDescription.compareMimeTypes(uriType, supportedType)) { - int flags = 0; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { - flags |= InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION; - } else { - grantUriPermission(editorInfo.packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - final InputContentInfoCompat inputContentInfo = new InputContentInfoCompat - (uri, new ClipDescription(uriType, new String[]{uriType}), null); - mInputLogic.mConnection.commitUri(inputContentInfo, editorInfo, flags); - return; - } + if (!EditorInfoCompatUtils.isMimeTypeSupportedByEditor(editorInfo, uriType)) { + mKeyboardSwitcher.showToast(getString(R.string.toast_msg_unsupported_uri), true); + return; + } + final boolean permissionGranted; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { + grantUriPermission(editorInfo.packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + permissionGranted = true; + } else { + permissionGranted = false; + } + // Commit the URI and show a toast if it is rejected by the application. + if (!mInputLogic.mConnection.commitUri(uri, uriType, editorInfo, permissionGranted)) { + mKeyboardSwitcher.showToast(getString(R.string.toast_msg_unsupported_uri), true); } - //TODO: inform the user with a toast-like message regarding an unsupported MIME type - Log.w(TAG, "URI MIME type " + uriType + " is incompatible with the current editor"); } public void onStartBatchInput() { diff --git a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java index 0225d7e93..c15fe7583 100644 --- a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java +++ b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java @@ -7,9 +7,11 @@ package helium314.keyboard.latin; import android.content.ClipData; +import android.content.ClipDescription; 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; @@ -344,16 +346,21 @@ public void commitText(final CharSequence text, final int newCursorPosition) { /** * Calls {@link InputConnectionCompat#commitContent(InputConnection, EditorInfo, InputContentInfoCompat, int, Bundle)}. * - * @param inputContentInfo The input content info to be committed. + * @param uri The URI to be committed. + * @param uriType The MIME type of the URI. * @param editorInfo The current editor info. + * @param permissionGranted Whether URI permission has already been granted. + * @return true if this request is accepted by the application, false otherwise. */ - public void commitUri(@NonNull final InputContentInfoCompat inputContentInfo, - @NonNull final EditorInfo editorInfo, final int flags) { + public boolean commitUri(final Uri uri, final String uriType, + final EditorInfo editorInfo, final boolean permissionGranted) { mIC = mParent.getCurrentInputConnection(); - if (isConnected()) { - mIC.finishComposingText(); - InputConnectionCompat.commitContent(mIC, editorInfo, inputContentInfo, flags, null); - } + if (!isConnected()) return false; + final InputContentInfoCompat inputContentInfo = new InputContentInfoCompat + (uri, new ClipDescription(uriType, new String[]{uriType}), null); + final int flags = permissionGranted ? 0 : InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION; + finishComposingText(); + return InputConnectionCompat.commitContent(mIC, editorInfo, inputContentInfo, flags, null); } @Nullable 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 69fb910b3..fa6458313 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -7,6 +7,7 @@ package helium314.keyboard.latin.inputlogic; import android.graphics.Color; +import android.net.Uri; import android.os.SystemClock; import android.text.InputType; import android.text.SpannableString; @@ -642,6 +643,23 @@ private void handleConsumedEvent(final Event event, final InputTransaction input } } + /** + * Handles the action of pasting content from the clipboard. + * Retrieves content from the clipboard history manager and commits it to the input connection. + * + */ + private void handleClipboardPaste() { + final Uri clipboardURI = mLatinIME.getClipboardHistoryManager().retrieveClipboardUri(); + if (clipboardURI != null) { + mLatinIME.onUriInput(clipboardURI); + } else { + final CharSequence clipboardText = mLatinIME.getClipboardHistoryManager().retrieveClipboardText(); + if (!TextUtils.isEmpty(clipboardText)) { + mLatinIME.onTextInput(clipboardText.toString()); + } + } + } + /** * Handle a functional key event. *

@@ -691,11 +709,11 @@ private void handleFunctionalEvent(final Event event, final InputTransaction inp // is being handled in {@link KeyboardState#onEvent(Event,int)}. // If disabled, current clipboard content is committed. if (!inputTransaction.getMSettingsValues().mClipboardHistoryEnabled) { - mLatinIME.getClipboardHistoryManager().pasteClipboard(); + handleClipboardPaste(); } break; case KeyCode.CLIPBOARD_PASTE: - mLatinIME.getClipboardHistoryManager().pasteClipboard(); + handleClipboardPaste(); break; case KeyCode.SHIFT_ENTER: // todo: try using sendDownUpKeyEventWithMetaState() and remove the key code maybe diff --git a/app/src/main/res/layout/clipboard_entry_key.xml b/app/src/main/res/layout/clipboard_entry_key.xml index fbb6ab6fd..e554a761c 100644 --- a/app/src/main/res/layout/clipboard_entry_key.xml +++ b/app/src/main/res/layout/clipboard_entry_key.xml @@ -15,32 +15,16 @@ android:layout_marginVertical="8dp" android:layout_marginStart="4dp"/> - + android:maxLines="4" + style="@style/ClipboardHistoryEntry" /> + style="@style/ClipboardHistoryEntry" /> \ No newline at end of file diff --git a/app/src/main/res/values/config.xml b/app/src/main/res/values/config.xml index bc22125eb..d3886dbbe 100644 --- a/app/src/main/res/values/config.xml +++ b/app/src/main/res/values/config.xml @@ -63,6 +63,12 @@ 18dp 27dp + + 4dp + 6dp + 8dp + 180dp + 24dp 73dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 21806019e..125b0a162 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -832,4 +832,6 @@ New dictionary: Reverse direction when a right-to-left keyboard subtype is selected Content copied + + Unsupported URI diff --git a/app/src/main/res/values/themes-common.xml b/app/src/main/res/values/themes-common.xml index 24e4a4b4c..1d7db82a2 100644 --- a/app/src/main/res/values/themes-common.xml +++ b/app/src/main/res/values/themes-common.xml @@ -97,6 +97,19 @@ attributes. -->