123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842 |
- // SPDX-License-Identifier: Unlicense OR MIT
- package org.gioui;
- import java.lang.Class;
- import java.lang.IllegalAccessException;
- import java.lang.InstantiationException;
- import java.lang.ExceptionInInitializerError;
- import java.lang.SecurityException;
- import android.app.Activity;
- import android.app.Fragment;
- import android.app.FragmentManager;
- import android.app.FragmentTransaction;
- import android.content.Context;
- import android.graphics.Canvas;
- import android.graphics.Color;
- import android.graphics.Matrix;
- import android.graphics.Rect;
- import android.os.Build;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.SystemClock;
- import android.text.TextUtils;
- import android.text.Selection;
- import android.text.SpannableStringBuilder;
- import android.util.AttributeSet;
- import android.util.TypedValue;
- import android.view.Choreographer;
- import android.view.Display;
- import android.view.KeyCharacterMap;
- import android.view.KeyEvent;
- import android.view.MotionEvent;
- import android.view.PointerIcon;
- import android.view.View;
- import android.view.ViewConfiguration;
- import android.view.WindowInsets;
- import android.view.Surface;
- import android.view.SurfaceView;
- import android.view.SurfaceHolder;
- import android.view.Window;
- import android.view.WindowInsetsController;
- import android.view.WindowManager;
- import android.view.inputmethod.CorrectionInfo;
- import android.view.inputmethod.CompletionInfo;
- import android.view.inputmethod.CursorAnchorInfo;
- 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 android.view.inputmethod.InputContentInfo;
- import android.view.inputmethod.SurroundingText;
- import android.view.accessibility.AccessibilityNodeProvider;
- import android.view.accessibility.AccessibilityNodeInfo;
- import android.view.accessibility.AccessibilityEvent;
- import android.view.accessibility.AccessibilityManager;
- import java.io.UnsupportedEncodingException;
- public final class GioView extends SurfaceView implements Choreographer.FrameCallback {
- private static boolean jniLoaded;
- private final SurfaceHolder.Callback surfCallbacks;
- private final View.OnFocusChangeListener focusCallback;
- private final InputMethodManager imm;
- private final float scrollXScale;
- private final float scrollYScale;
- private final AccessibilityManager accessManager;
- private int keyboardHint;
- private long nhandle;
- public GioView(Context context) {
- this(context, null);
- }
- public GioView(Context context, AttributeSet attrs) {
- super(context, attrs);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
- }
- setLayoutParams(new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT));
- // Late initialization of the Go runtime to wait for a valid context.
- Gio.init(context.getApplicationContext());
- // Set background color to transparent to avoid a flickering
- // issue on ChromeOS.
- setBackgroundColor(Color.argb(0, 0, 0, 0));
- ViewConfiguration conf = ViewConfiguration.get(context);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- scrollXScale = conf.getScaledHorizontalScrollFactor();
- scrollYScale = conf.getScaledVerticalScrollFactor();
- // The platform focus highlight is not aware of Gio's widgets.
- setDefaultFocusHighlightEnabled(false);
- } else {
- float listItemHeight = 48; // dp
- float px = TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP,
- listItemHeight,
- getResources().getDisplayMetrics()
- );
- scrollXScale = px;
- scrollYScale = px;
- }
- setHighRefreshRate();
- accessManager = (AccessibilityManager)context.getSystemService(Context.ACCESSIBILITY_SERVICE);
- imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
- nhandle = onCreateView(this);
- setFocusable(true);
- setFocusableInTouchMode(true);
- focusCallback = new View.OnFocusChangeListener() {
- @Override public void onFocusChange(View v, boolean focus) {
- GioView.this.onFocusChange(nhandle, focus);
- }
- };
- setOnFocusChangeListener(focusCallback);
- surfCallbacks = new SurfaceHolder.Callback() {
- @Override public void surfaceCreated(SurfaceHolder holder) {
- // Ignore; surfaceChanged is guaranteed to be called immediately after this.
- }
- @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
- onSurfaceChanged(nhandle, getHolder().getSurface());
- }
- @Override public void surfaceDestroyed(SurfaceHolder holder) {
- onSurfaceDestroyed(nhandle);
- }
- };
- getHolder().addCallback(surfCallbacks);
- }
- @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (nhandle != 0) {
- onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), true, event.getEventTime());
- }
- return false;
- }
- @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (nhandle != 0) {
- onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), false, event.getEventTime());
- }
- return false;
- }
- @Override public boolean onGenericMotionEvent(MotionEvent event) {
- dispatchMotionEvent(event);
- return true;
- }
- @Override public boolean onTouchEvent(MotionEvent event) {
- // Ask for unbuffered events. Flutter and Chrome do it
- // so assume it's good for us as well.
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- requestUnbufferedDispatch(event);
- }
- dispatchMotionEvent(event);
- return true;
- }
- private void setCursor(int id) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
- return;
- }
- PointerIcon pointerIcon = PointerIcon.getSystemIcon(getContext(), id);
- setPointerIcon(pointerIcon);
- }
- private void setOrientation(int id, int fallback) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
- id = fallback;
- }
- ((Activity) this.getContext()).setRequestedOrientation(id);
- }
- private void setFullscreen(boolean enabled) {
- int flags = this.getSystemUiVisibility();
- if (enabled) {
- flags |= SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
- flags |= SYSTEM_UI_FLAG_HIDE_NAVIGATION;
- flags |= SYSTEM_UI_FLAG_FULLSCREEN;
- flags |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
- } else {
- flags &= ~SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
- flags &= ~SYSTEM_UI_FLAG_HIDE_NAVIGATION;
- flags &= ~SYSTEM_UI_FLAG_FULLSCREEN;
- flags &= ~SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
- }
- this.setSystemUiVisibility(flags);
- }
- private enum Bar {
- NAVIGATION,
- STATUS,
- }
- private void setBarColor(Bar t, int color, int luminance) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
- return;
- }
- Window window = ((Activity) this.getContext()).getWindow();
- int insetsMask;
- int viewMask;
- switch (t) {
- case STATUS:
- insetsMask = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
- viewMask = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
- window.setStatusBarColor(color);
- break;
- case NAVIGATION:
- insetsMask = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
- viewMask = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
- window.setNavigationBarColor(color);
- break;
- default:
- throw new RuntimeException("invalid bar type");
- }
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
- return;
- }
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
- int flags = this.getSystemUiVisibility();
- if (luminance > 128) {
- flags |= viewMask;
- } else {
- flags &= ~viewMask;
- }
- this.setSystemUiVisibility(flags);
- return;
- }
- WindowInsetsController insetsController = window.getInsetsController();
- if (insetsController == null) {
- return;
- }
- if (luminance > 128) {
- insetsController.setSystemBarsAppearance(insetsMask, insetsMask);
- } else {
- insetsController.setSystemBarsAppearance(0, insetsMask);
- }
- }
- private void setStatusColor(int color, int luminance) {
- this.setBarColor(Bar.STATUS, color, luminance);
- }
- private void setNavigationColor(int color, int luminance) {
- this.setBarColor(Bar.NAVIGATION, color, luminance);
- }
- private void setHighRefreshRate() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
- return;
- }
- Context context = getContext();
- Display display = context.getDisplay();
- Display.Mode[] supportedModes = display.getSupportedModes();
- if (supportedModes.length <= 1) {
- // Nothing to set
- return;
- }
- Display.Mode currentMode = display.getMode();
- int currentWidth = currentMode.getPhysicalWidth();
- int currentHeight = currentMode.getPhysicalHeight();
- float minRefreshRate = -1;
- float maxRefreshRate = -1;
- float bestRefreshRate = -1;
- int bestModeId = -1;
- for (Display.Mode mode : supportedModes) {
- float refreshRate = mode.getRefreshRate();
- float width = mode.getPhysicalWidth();
- float height = mode.getPhysicalHeight();
- if (minRefreshRate == -1 || refreshRate < minRefreshRate) {
- minRefreshRate = refreshRate;
- }
- if (maxRefreshRate == -1 || refreshRate > maxRefreshRate) {
- maxRefreshRate = refreshRate;
- }
- boolean refreshRateIsBetter = bestRefreshRate == -1 || refreshRate > bestRefreshRate;
- if (width == currentWidth && height == currentHeight && refreshRateIsBetter) {
- int modeId = mode.getModeId();
- bestRefreshRate = refreshRate;
- bestModeId = modeId;
- }
- }
- if (bestModeId == -1) {
- // Not expecting this but just in case
- return;
- }
- if (minRefreshRate == maxRefreshRate) {
- // Can't improve the refresh rate
- return;
- }
- Window window = ((Activity) context).getWindow();
- WindowManager.LayoutParams layoutParams = window.getAttributes();
- layoutParams.preferredDisplayModeId = bestModeId;
- window.setAttributes(layoutParams);
- }
- @Override protected boolean dispatchHoverEvent(MotionEvent event) {
- if (!accessManager.isTouchExplorationEnabled()) {
- return super.dispatchHoverEvent(event);
- }
- switch (event.getAction()) {
- case MotionEvent.ACTION_HOVER_ENTER:
- // Fall through.
- case MotionEvent.ACTION_HOVER_MOVE:
- onTouchExploration(nhandle, event.getX(), event.getY());
- break;
- case MotionEvent.ACTION_HOVER_EXIT:
- onExitTouchExploration(nhandle);
- break;
- }
- return true;
- }
- void sendA11yEvent(int eventType, int viewId) {
- if (!accessManager.isEnabled()) {
- return;
- }
- AccessibilityEvent event = obtainA11yEvent(eventType, viewId);
- getParent().requestSendAccessibilityEvent(this, event);
- }
- AccessibilityEvent obtainA11yEvent(int eventType, int viewId) {
- AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
- event.setPackageName(getContext().getPackageName());
- event.setSource(this, viewId);
- return event;
- }
- boolean isA11yActive() {
- return accessManager.isEnabled();
- }
- void sendA11yChange(int viewId) {
- if (!accessManager.isEnabled()) {
- return;
- }
- AccessibilityEvent event = obtainA11yEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED, viewId);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
- }
- getParent().requestSendAccessibilityEvent(this, event);
- }
- private void dispatchMotionEvent(MotionEvent event) {
- if (nhandle == 0) {
- return;
- }
- for (int j = 0; j < event.getHistorySize(); j++) {
- long time = event.getHistoricalEventTime(j);
- for (int i = 0; i < event.getPointerCount(); i++) {
- onTouchEvent(
- nhandle,
- event.ACTION_MOVE,
- event.getPointerId(i),
- event.getToolType(i),
- event.getHistoricalX(i, j),
- event.getHistoricalY(i, j),
- scrollXScale*event.getHistoricalAxisValue(MotionEvent.AXIS_HSCROLL, i, j),
- scrollYScale*event.getHistoricalAxisValue(MotionEvent.AXIS_VSCROLL, i, j),
- event.getButtonState(),
- time);
- }
- }
- int act = event.getActionMasked();
- int idx = event.getActionIndex();
- for (int i = 0; i < event.getPointerCount(); i++) {
- int pact = event.ACTION_MOVE;
- if (i == idx) {
- pact = act;
- }
- onTouchEvent(
- nhandle,
- pact,
- event.getPointerId(i),
- event.getToolType(i),
- event.getX(i), event.getY(i),
- scrollXScale*event.getAxisValue(MotionEvent.AXIS_HSCROLL, i),
- scrollYScale*event.getAxisValue(MotionEvent.AXIS_VSCROLL, i),
- event.getButtonState(),
- event.getEventTime());
- }
- }
- @Override public InputConnection onCreateInputConnection(EditorInfo editor) {
- Snippet snip = getSnippet();
- editor.inputType = this.keyboardHint;
- editor.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
- editor.initialSelStart = imeToUTF16(nhandle, imeSelectionStart(nhandle));
- editor.initialSelEnd = imeToUTF16(nhandle, imeSelectionEnd(nhandle));
- int selStart = editor.initialSelStart - snip.offset;
- editor.initialCapsMode = TextUtils.getCapsMode(snip.snippet, selStart, this.keyboardHint);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- editor.setInitialSurroundingSubText(snip.snippet, imeToUTF16(nhandle, snip.offset));
- }
- imeSetComposingRegion(nhandle, -1, -1);
- return new GioInputConnection();
- }
- void setInputHint(int hint) {
- if (hint == this.keyboardHint) {
- return;
- }
- this.keyboardHint = hint;
- restartInput();
- }
- void showTextInput() {
- GioView.this.requestFocus();
- imm.showSoftInput(GioView.this, 0);
- }
- void hideTextInput() {
- imm.hideSoftInputFromWindow(getWindowToken(), 0);
- }
- @Override protected boolean fitSystemWindows(Rect insets) {
- if (nhandle != 0) {
- onWindowInsets(nhandle, insets.top, insets.right, insets.bottom, insets.left);
- }
- return true;
- }
- void postFrameCallback() {
- Choreographer.getInstance().removeFrameCallback(this);
- Choreographer.getInstance().postFrameCallback(this);
- }
- @Override public void doFrame(long nanos) {
- if (nhandle != 0) {
- onFrameCallback(nhandle);
- }
- }
- int getDensity() {
- return getResources().getDisplayMetrics().densityDpi;
- }
- float getFontScale() {
- return getResources().getConfiguration().fontScale;
- }
- public void start() {
- if (nhandle != 0) {
- onStartView(nhandle);
- }
- }
- public void stop() {
- if (nhandle != 0) {
- onStopView(nhandle);
- }
- }
- public void destroy() {
- if (nhandle != 0) {
- onDestroyView(nhandle);
- }
- }
- protected void unregister() {
- setOnFocusChangeListener(null);
- getHolder().removeCallback(surfCallbacks);
- nhandle = 0;
- }
- public void configurationChanged() {
- if (nhandle != 0) {
- onConfigurationChanged(nhandle);
- }
- }
- public boolean backPressed() {
- if (nhandle == 0) {
- return false;
- }
- return onBack(nhandle);
- }
- void restartInput() {
- imm.restartInput(this);
- }
- void updateSelection() {
- int selStart = imeToUTF16(nhandle, imeSelectionStart(nhandle));
- int selEnd = imeToUTF16(nhandle, imeSelectionEnd(nhandle));
- int compStart = imeToUTF16(nhandle, imeComposingStart(nhandle));
- int compEnd = imeToUTF16(nhandle, imeComposingEnd(nhandle));
- imm.updateSelection(this, selStart, selEnd, compStart, compEnd);
- }
- void updateCaret(float m00, float m01, float m02, float m10, float m11, float m12, float caretX, float caretTop, float caretBase, float caretBottom) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
- return;
- }
- Matrix m = new Matrix();
- m.setValues(new float[]{m00, m01, m02, m10, m11, m12, 0.0f, 0.0f, 1.0f});
- m.setConcat(getMatrix(), m);
- int selStart = imeSelectionStart(nhandle);
- int selEnd = imeSelectionEnd(nhandle);
- int compStart = imeComposingStart(nhandle);
- int compEnd = imeComposingEnd(nhandle);
- Snippet snip = getSnippet();
- String composing = "";
- if (compStart != -1) {
- composing = snip.substringRunes(compStart, compEnd);
- }
- CursorAnchorInfo inf = new CursorAnchorInfo.Builder()
- .setMatrix(m)
- .setComposingText(imeToUTF16(nhandle, compStart), composing)
- .setSelectionRange(imeToUTF16(nhandle, selStart), imeToUTF16(nhandle, selEnd))
- .setInsertionMarkerLocation(caretX, caretTop, caretBase, caretBottom, 0)
- .build();
- imm.updateCursorAnchorInfo(this, inf);
- }
- static private native long onCreateView(GioView view);
- static private native void onDestroyView(long handle);
- static private native void onStartView(long handle);
- static private native void onStopView(long handle);
- static private native void onSurfaceDestroyed(long handle);
- static private native void onSurfaceChanged(long handle, Surface surface);
- static private native void onConfigurationChanged(long handle);
- static private native void onWindowInsets(long handle, int top, int right, int bottom, int left);
- static public native void onLowMemory();
- static private native void onTouchEvent(long handle, int action, int pointerID, int tool, float x, float y, float scrollX, float scrollY, int buttons, long time);
- static private native void onKeyEvent(long handle, int code, int character, boolean pressed, long time);
- static private native void onFrameCallback(long handle);
- static private native boolean onBack(long handle);
- static private native void onFocusChange(long handle, boolean focus);
- static private native AccessibilityNodeInfo initializeAccessibilityNodeInfo(long handle, int viewId, int screenX, int screenY, AccessibilityNodeInfo info);
- static private native void onTouchExploration(long handle, float x, float y);
- static private native void onExitTouchExploration(long handle);
- static private native void onA11yFocus(long handle, int viewId);
- static private native void onClearA11yFocus(long handle, int viewId);
- static private native void imeSetSnippet(long handle, int start, int end);
- static private native String imeSnippet(long handle);
- static private native int imeSnippetStart(long handle);
- static private native int imeSelectionStart(long handle);
- static private native int imeSelectionEnd(long handle);
- static private native int imeComposingStart(long handle);
- static private native int imeComposingEnd(long handle);
- static private native int imeReplace(long handle, int start, int end, String text);
- static private native int imeSetSelection(long handle, int start, int end);
- static private native int imeSetComposingRegion(long handle, int start, int end);
- // imeToRunes converts the Java character index into runes (Java code points).
- static private native int imeToRunes(long handle, int chars);
- // imeToUTF16 converts the rune index into Java characters.
- static private native int imeToUTF16(long handle, int runes);
- private class GioInputConnection implements InputConnection {
- private int batchDepth;
- @Override public boolean beginBatchEdit() {
- batchDepth++;
- return true;
- }
- @Override public boolean endBatchEdit() {
- batchDepth--;
- return batchDepth > 0;
- }
- @Override public boolean clearMetaKeyStates(int states) {
- return false;
- }
- @Override public boolean commitCompletion(CompletionInfo text) {
- return false;
- }
- @Override public boolean commitCorrection(CorrectionInfo info) {
- return false;
- }
- @Override public boolean commitText(CharSequence text, int cursor) {
- setComposingText(text, cursor);
- return finishComposingText();
- }
- @Override public boolean deleteSurroundingText(int beforeChars, int afterChars) {
- // translate before and after to runes.
- int selStart = imeSelectionStart(nhandle);
- int selEnd = imeSelectionEnd(nhandle);
- int before = selStart - imeToRunes(nhandle, imeToUTF16(nhandle, selStart) - beforeChars);
- int after = selEnd - imeToRunes(nhandle, imeToUTF16(nhandle, selEnd) - afterChars);
- return deleteSurroundingTextInCodePoints(before, after);
- }
- @Override public boolean finishComposingText() {
- imeSetComposingRegion(nhandle, -1, -1);
- return true;
- }
- @Override public int getCursorCapsMode(int reqModes) {
- Snippet snip = getSnippet();
- int selStart = imeSelectionStart(nhandle);
- int off = imeToUTF16(nhandle, selStart - snip.offset);
- if (off < 0 || off > snip.snippet.length()) {
- return 0;
- }
- return TextUtils.getCapsMode(snip.snippet, off, reqModes);
- }
- @Override public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
- return null;
- }
- @Override public CharSequence getSelectedText(int flags) {
- Snippet snip = getSnippet();
- int selStart = imeSelectionStart(nhandle);
- int selEnd = imeSelectionEnd(nhandle);
- String sub = snip.substringRunes(selStart, selEnd);
- return sub;
- }
- @Override public CharSequence getTextAfterCursor(int n, int flags) {
- Snippet snip = getSnippet();
- int selStart = imeSelectionStart(nhandle);
- int selEnd = imeSelectionEnd(nhandle);
- // n are in Java characters, but in worst case we'll just ask for more runes
- // than wanted.
- imeSetSnippet(nhandle, selStart - n, selEnd + n);
- int start = selEnd;
- int end = imeToRunes(nhandle, imeToUTF16(nhandle, selEnd) + n);
- String ret = snip.substringRunes(start, end);
- return ret;
- }
- @Override public CharSequence getTextBeforeCursor(int n, int flags) {
- Snippet snip = getSnippet();
- int selStart = imeSelectionStart(nhandle);
- int selEnd = imeSelectionEnd(nhandle);
- // n are in Java characters, but in worst case we'll just ask for more runes
- // than wanted.
- imeSetSnippet(nhandle, selStart - n, selEnd + n);
- int start = imeToRunes(nhandle, imeToUTF16(nhandle, selStart) - n);
- int end = selStart;
- String ret = snip.substringRunes(start, end);
- return ret;
- }
- @Override public boolean performContextMenuAction(int id) {
- return false;
- }
- @Override public boolean performEditorAction(int editorAction) {
- long eventTime = SystemClock.uptimeMillis();
- // Translate to enter key.
- onKeyEvent(nhandle, KeyEvent.KEYCODE_ENTER, '\n', true, eventTime);
- onKeyEvent(nhandle, KeyEvent.KEYCODE_ENTER, '\n', false, eventTime);
- return true;
- }
- @Override public boolean performPrivateCommand(String action, Bundle data) {
- return false;
- }
- @Override public boolean reportFullscreenMode(boolean enabled) {
- return false;
- }
- @Override public boolean sendKeyEvent(KeyEvent event) {
- boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN;
- onKeyEvent(nhandle, event.getKeyCode(), event.getUnicodeChar(), pressed, event.getEventTime());
- return true;
- }
- @Override public boolean setComposingRegion(int startChars, int endChars) {
- int compStart = imeToRunes(nhandle, startChars);
- int compEnd = imeToRunes(nhandle, endChars);
- imeSetComposingRegion(nhandle, compStart, compEnd);
- return true;
- }
- @Override public boolean setComposingText(CharSequence text, int relCursor) {
- int start = imeComposingStart(nhandle);
- int end = imeComposingEnd(nhandle);
- if (start == -1 || end == -1) {
- start = imeSelectionStart(nhandle);
- end = imeSelectionEnd(nhandle);
- }
- String str = text.toString();
- imeReplace(nhandle, start, end, str);
- int cursor = start;
- int runes = str.codePointCount(0, str.length());
- if (relCursor > 0) {
- cursor += runes;
- relCursor--;
- }
- imeSetComposingRegion(nhandle, start, start + runes);
- // Move cursor.
- Snippet snip = getSnippet();
- cursor = imeToRunes(nhandle, imeToUTF16(nhandle, cursor) + relCursor);
- imeSetSelection(nhandle, cursor, cursor);
- return true;
- }
- @Override public boolean setSelection(int startChars, int endChars) {
- int start = imeToRunes(nhandle, startChars);
- int end = imeToRunes(nhandle, endChars);
- imeSetSelection(nhandle, start, end);
- return true;
- }
- /*@Override*/ public boolean requestCursorUpdates(int cursorUpdateMode) {
- // We always provide cursor updates.
- return true;
- }
- /*@Override*/ public void closeConnection() {
- }
- /*@Override*/ public Handler getHandler() {
- return null;
- }
- /*@Override*/ public boolean commitContent(InputContentInfo info, int flags, Bundle opts) {
- return false;
- }
- /*@Override*/ public boolean deleteSurroundingTextInCodePoints(int before, int after) {
- if (after > 0) {
- int selEnd = imeSelectionEnd(nhandle);
- imeReplace(nhandle, selEnd, selEnd + after, "");
- }
- if (before > 0) {
- int selStart = imeSelectionStart(nhandle);
- imeReplace(nhandle, selStart - before, selStart, "");
- }
- return true;
- }
- /*@Override*/ public SurroundingText getSurroundingText(int beforeChars, int afterChars, int flags) {
- Snippet snip = getSnippet();
- int selStart = imeSelectionStart(nhandle);
- int selEnd = imeSelectionEnd(nhandle);
- // Expanding in Java characters is ok.
- imeSetSnippet(nhandle, selStart - beforeChars, selEnd + afterChars);
- return new SurroundingText(snip.snippet, imeToUTF16(nhandle, selStart), imeToUTF16(nhandle, selEnd), imeToUTF16(nhandle, snip.offset));
- }
- }
- private Snippet getSnippet() {
- Snippet snip = new Snippet();
- snip.snippet = imeSnippet(nhandle);
- snip.offset = imeSnippetStart(nhandle);
- return snip;
- }
- // Snippet is like android.view.inputmethod.SurroundingText but available for Android < 31.
- private static class Snippet {
- String snippet;
- // offset of snippet into the entire editor content. It is in runes because we won't require
- // Gio editors to keep track of UTF-16 offsets. The distinction won't matter in practice because IMEs only
- // ever see snippets.
- int offset;
- // substringRunes returns the substring from start to end in runes. The resuls is
- // truncated to the snippet.
- String substringRunes(int start, int end) {
- start -= this.offset;
- end -= this.offset;
- int runes = snippet.codePointCount(0, snippet.length());
- if (start < 0) {
- start = 0;
- }
- if (end < 0) {
- end = 0;
- }
- if (start > runes) {
- start = runes;
- }
- if (end > runes) {
- end = runes;
- }
- return snippet.substring(
- snippet.offsetByCodePoints(0, start),
- snippet.offsetByCodePoints(0, end)
- );
- }
- }
- @Override public AccessibilityNodeProvider getAccessibilityNodeProvider() {
- return new AccessibilityNodeProvider() {
- private final int[] screenOff = new int[2];
- @Override public AccessibilityNodeInfo createAccessibilityNodeInfo(int viewId) {
- AccessibilityNodeInfo info = null;
- if (viewId == View.NO_ID) {
- info = AccessibilityNodeInfo.obtain(GioView.this);
- GioView.this.onInitializeAccessibilityNodeInfo(info);
- } else {
- info = AccessibilityNodeInfo.obtain(GioView.this, viewId);
- info.setPackageName(getContext().getPackageName());
- info.setVisibleToUser(true);
- }
- GioView.this.getLocationOnScreen(screenOff);
- info = GioView.this.initializeAccessibilityNodeInfo(nhandle, viewId, screenOff[0], screenOff[1], info);
- return info;
- }
- @Override public boolean performAction(int viewId, int action, Bundle arguments) {
- if (viewId == View.NO_ID) {
- return GioView.this.performAccessibilityAction(action, arguments);
- }
- switch (action) {
- case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
- GioView.this.onA11yFocus(nhandle, viewId);
- GioView.this.sendA11yEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, viewId);
- return true;
- case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
- GioView.this.onClearA11yFocus(nhandle, viewId);
- GioView.this.sendA11yEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, viewId);
- return true;
- }
- return false;
- }
- };
- }
- }
|