Skip to content

Commit

Permalink
improve code readability & show toast if URI is incompatible
Browse files Browse the repository at this point in the history
  • Loading branch information
codokie committed May 31, 2024
1 parent ddd3043 commit 27f1781
Show file tree
Hide file tree
Showing 12 changed files with 113 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -65,7 +65,7 @@ class ClipboardAdapter(
visibility = View.GONE
setImageResource(pinnedIconResId)
}
contentView = view.findViewById<TextView>(R.id.clipboard_entry_content).apply {
textView = view.findViewById<TextView>(R.id.clipboard_entry_text).apply {
typeface = itemTypeFace
setTextColor(itemTextColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX, itemTextSize)
Expand All @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,46 +45,39 @@ 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)
onHistoryChangeListener?.onClipboardHistoryEntryAdded(at)
}
}

// 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
}

Expand Down Expand Up @@ -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? {
Expand All @@ -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)
Expand Down
35 changes: 14 additions & 21 deletions app/src/main/java/helium314/keyboard/latin/LatinIME.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
* <p>
Expand Down Expand Up @@ -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
Expand Down
26 changes: 5 additions & 21 deletions app/src/main/res/layout/clipboard_entry_key.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,16 @@
android:layout_marginVertical="8dp"
android:layout_marginStart="4dp"/>

<!-- Provide audio and haptic feedback by ourselves based on the keyboard settings.
We just need to ignore the system's audio and haptic feedback settings. -->
<TextView
android:id="@+id/clipboard_entry_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginTop="4dp"
android:layout_marginBottom="6dp"
android:layout_marginHorizontal="8dp"
android:hapticFeedbackEnabled="false"
android:soundEffectsEnabled="false"
android:id="@+id/clipboard_entry_text"
android:ellipsize="end"
android:maxLines="4"/>
android:maxLines="4"
style="@style/ClipboardHistoryEntry" />

<ImageView
android:id="@+id/clipboard_entry_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="200dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="6dp"
android:layout_marginHorizontal="8dp"
android:hapticFeedbackEnabled="false"
android:soundEffectsEnabled="false"
android:maxHeight="@dimen/config_clipboard_history_entry_image_max_height"
android:adjustViewBounds="true"
android:scaleType="centerCrop" />
style="@style/ClipboardHistoryEntry" />

</LinearLayout>
6 changes: 6 additions & 0 deletions app/src/main/res/values/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@
<dimen name="config_suggestion_text_size">18dp</dimen>
<dimen name="config_more_suggestions_hint_text_size">27dp</dimen>

<!-- Clipboard history entry parameters -->
<dimen name="config_clipboard_history_entry_top_margin">4dp</dimen>
<dimen name="config_clipboard_history_entry_bottom_margin">6dp</dimen>
<dimen name="config_clipboard_history_entry_horizontal_margin">8dp</dimen>
<dimen name="config_clipboard_history_entry_image_max_height">180dp</dimen>

<!-- Gesture floating preview text parameters -->
<dimen name="config_gesture_floating_preview_text_size">24dp</dimen>
<dimen name="config_gesture_floating_preview_text_offset">73dp</dimen>
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -832,4 +832,6 @@ New dictionary:
<string name="var_toolbar_direction_summary">Reverse direction when a right-to-left keyboard subtype is selected</string>
<!-- Toast message shown when content is copied to the clipboard -->
<string name="toast_msg_clipboard_copy">Content copied</string>
<!-- Toast message to inform the user that the URI is unsupported -->
<string name="toast_msg_unsupported_uri">Unsupported URI</string>
</resources>
13 changes: 13 additions & 0 deletions app/src/main/res/values/themes-common.xml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,19 @@
attributes. -->
<style name="EmojiPalettesView" />
<style name="ClipboardHistoryView" />
<style name="ClipboardHistoryEntry" >
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">1</item>
<item name="android:layout_marginTop">@dimen/config_clipboard_history_entry_top_margin</item>
<item name="android:layout_marginBottom">@dimen/config_clipboard_history_entry_bottom_margin</item>
<item name="android:layout_marginRight">@dimen/config_clipboard_history_entry_horizontal_margin</item>
<item name="android:layout_marginLeft">@dimen/config_clipboard_history_entry_horizontal_margin</item>
<!-- Provide audio and haptic feedback by ourselves based on the keyboard settings.
We just need to ignore the system's audio and haptic feedback settings. -->
<item name="android:hapticFeedbackEnabled">false</item>
<item name="android:soundEffectsEnabled">false</item>
</style>
<style name="PopupKeysKeyboardView" />
<style name="SuggestionStripView" />
<style name="SuggestionWord">
Expand Down

0 comments on commit 27f1781

Please sign in to comment.