-
Notifications
You must be signed in to change notification settings - Fork 6k
Add image keyboard support on Android #27763
Changes from all commits
54a9657
5f0582e
d2ab746
983c07c
89ca066
b4c40dd
17e900e
e414b03
805f6f8
9123435
26d13e0
58c5299
9d0eb17
bbb3286
fa50825
0c1dbbd
a933522
3cead60
4f735ff
76f5faf
5d6d517
6004596
0224531
f1d7ab0
41fe8f0
4105ebc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,9 +4,11 @@ | |
|
||
package io.flutter.plugin.editing; | ||
|
||
import android.annotation.TargetApi; | ||
import android.content.ClipData; | ||
import android.content.ClipboardManager; | ||
import android.content.Context; | ||
import android.net.Uri; | ||
import android.os.Build; | ||
import android.os.Bundle; | ||
import android.text.DynamicLayout; | ||
|
@@ -22,11 +24,20 @@ | |
import android.view.inputmethod.EditorInfo; | ||
import android.view.inputmethod.ExtractedText; | ||
import android.view.inputmethod.ExtractedTextRequest; | ||
import android.view.inputmethod.InputContentInfo; | ||
import android.view.inputmethod.InputMethodManager; | ||
import androidx.annotation.RequiresApi; | ||
import androidx.core.os.BuildCompat; | ||
import androidx.core.view.inputmethod.InputConnectionCompat; | ||
import io.flutter.Log; | ||
import io.flutter.embedding.android.KeyboardManager; | ||
import io.flutter.embedding.engine.FlutterJNI; | ||
import io.flutter.embedding.engine.systemchannels.TextInputChannel; | ||
import java.io.ByteArrayOutputStream; | ||
import java.io.FileNotFoundException; | ||
import java.io.InputStream; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
class InputConnectionAdaptor extends BaseInputConnection | ||
implements ListenableEditingState.EditingStateWatcher { | ||
|
@@ -473,6 +484,69 @@ public boolean performEditorAction(int actionCode) { | |
return true; | ||
} | ||
|
||
@Override | ||
@TargetApi(25) | ||
@RequiresApi(25) | ||
public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { | ||
// Ensure permission is granted. | ||
if (Build.VERSION.SDK_INT >= 25 | ||
&& (flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { | ||
try { | ||
inputContentInfo.requestPermission(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this request show a dialog for permissions? If so, I would recommend keeping all UI-related behavior out of this class. Consider making it the clients responsibility to obtain the appropriate permissions before running the code that requires the permissions. You can throw an exception if that permission isn't granted. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does not appear to show a dialog for any permissions. I think its more of asking the system to have temporary access to the committed files, if the app isn't given access already. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you tried running the code without the permissions to see what happens? If no dialog is shown, then who are you "requesting permission" from? Typically a "request" is something that can be denied, so this seems strange. |
||
} catch (Exception e) { | ||
return false; | ||
} | ||
} else { | ||
return false; | ||
} | ||
|
||
if (inputContentInfo.getDescription().getMimeTypeCount() > 0) { | ||
inputContentInfo.requestPermission(); | ||
|
||
final Uri uri = inputContentInfo.getContentUri(); | ||
final String mimeType = inputContentInfo.getDescription().getMimeType(0); | ||
Context context = mFlutterView.getContext(); | ||
Boolean retval = false; | ||
|
||
try { | ||
final InputStream is = context.getContentResolver().openInputStream(uri); | ||
final byte[] data = this.readStreamFully(is, 64 * 1024); | ||
|
||
final Map<String, Object> obj = new HashMap<>(); | ||
obj.put("mimeType", mimeType); | ||
obj.put("data", data); | ||
obj.put("uri", uri != null ? uri.toString() : null); | ||
|
||
// Commit the content to the text input channel | ||
textInputChannel.commitContent(mClient, obj); | ||
retval = true; | ||
} catch (FileNotFoundException ex) { | ||
} catch (Exception ex) { | ||
} finally { | ||
inputContentInfo.releasePermission(); | ||
} | ||
return retval; | ||
} | ||
return false; | ||
} | ||
|
||
public byte[] readStreamFully(InputStream is, int blocksize) { | ||
try { | ||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | ||
|
||
byte[] buffer = new byte[blocksize]; | ||
while (true) { | ||
int len = is.read(buffer); | ||
if (len == -1) break; | ||
baos.write(buffer, 0, len); | ||
} | ||
return baos.toByteArray(); | ||
} catch (Exception e) { | ||
} | ||
|
||
return new byte[0]; | ||
} | ||
|
||
// -------- Start: ListenableEditingState watcher implementation ------- | ||
@Override | ||
public void didChangeEditingState( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A good point was recently brought up by @matthew-carroll about how APIs like this should follow Flutter's declarative pattern instead of being imperative. So instead of telling the client to commit the content, informing the client that the user committed content (
contentCommitted
maybe?). Though, it seems that adjacent API methods already have an imperative pattern, so maybe now is not the time to change that.