Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.*

Expand Down Expand Up @@ -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<Locale> {
if (editorInfo == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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 }
Expand All @@ -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() {
Expand Down
21 changes: 21 additions & 0 deletions app/src/main/java/helium314/keyboard/latin/LatinIME.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,13 +25,16 @@
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;
import android.view.inputmethod.InputMethodManager;

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;
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/res/layout/clipboard_suggestion.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
android:paddingEnd="12dp"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:clipToOutline="true"
tools:ignore="RtlSymmetry">
<TextView
android:id="@+id/clipboard_suggestion_text"
Expand All @@ -22,6 +23,14 @@
android:ellipsize="end"
android:textStyle="bold"
style="?android:attr/textAppearanceSmall" />
<ImageView
android:id="@+id/clipboard_suggestion_image"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerInside"
android:paddingHorizontal="12dp"
android:visibility="gone" />
<ImageView
android:id="@+id/clipboard_suggestion_close"
android:contentDescription="@string/spoken_clipboard_suggestion"
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 @@ -903,6 +903,8 @@ New dictionary:
<string name="auto_hide_toolbar_summary">Hide the toolbar when suggestions become available</string>
<!-- Toast message shown when content is copied to the clipboard -->
<string name="toast_msg_clipboard_copy">Content copied</string>
<!-- Toast message shown when trying to paste a URI that the editor doesn't support -->
<string name="toast_msg_unsupported_uri">This app does not support pasting this content type</string>
<!-- Title of the setting to customize icons for keyboard and toolbar keys -->
<string name="customize_icons">Customize icons</string>
<!-- Confirmation message when resetting all custom icons -->
Expand Down