GioView.java raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 package org.gioui;
4
5 import java.lang.Class;
6 import java.lang.IllegalAccessException;
7 import java.lang.InstantiationException;
8 import java.lang.ExceptionInInitializerError;
9 import java.lang.SecurityException;
10 import android.app.Activity;
11 import android.app.Fragment;
12 import android.app.FragmentManager;
13 import android.app.FragmentTransaction;
14 import android.content.Context;
15 import android.graphics.Color;
16 import android.graphics.Rect;
17 import android.os.Build;
18 import android.text.Editable;
19 import android.util.AttributeSet;
20 import android.util.TypedValue;
21 import android.view.Choreographer;
22 import android.view.KeyCharacterMap;
23 import android.view.KeyEvent;
24 import android.view.MotionEvent;
25 import android.view.PointerIcon;
26 import android.view.View;
27 import android.view.ViewConfiguration;
28 import android.view.WindowInsets;
29 import android.view.Surface;
30 import android.view.SurfaceView;
31 import android.view.SurfaceHolder;
32 import android.view.inputmethod.BaseInputConnection;
33 import android.view.inputmethod.InputConnection;
34 import android.view.inputmethod.InputMethodManager;
35 import android.view.inputmethod.EditorInfo;
36
37 import java.io.UnsupportedEncodingException;
38
39 public final class GioView extends SurfaceView implements Choreographer.FrameCallback {
40 private static boolean jniLoaded;
41
42 private final SurfaceHolder.Callback surfCallbacks;
43 private final View.OnFocusChangeListener focusCallback;
44 private final InputMethodManager imm;
45 private final float scrollXScale;
46 private final float scrollYScale;
47
48 private long nhandle;
49
50 public GioView(Context context) {
51 this(context, null);
52 }
53
54 public GioView(Context context, AttributeSet attrs) {
55 super(context, attrs);
56
57 // Late initialization of the Go runtime to wait for a valid context.
58 Gio.init(context.getApplicationContext());
59
60 // Set background color to transparent to avoid a flickering
61 // issue on ChromeOS.
62 setBackgroundColor(Color.argb(0, 0, 0, 0));
63
64 ViewConfiguration conf = ViewConfiguration.get(context);
65 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
66 scrollXScale = conf.getScaledHorizontalScrollFactor();
67 scrollYScale = conf.getScaledVerticalScrollFactor();
68
69 // The platform focus highlight is not aware of Gio's widgets.
70 setDefaultFocusHighlightEnabled(false);
71 } else {
72 float listItemHeight = 48; // dp
73 float px = TypedValue.applyDimension(
74 TypedValue.COMPLEX_UNIT_DIP,
75 listItemHeight,
76 getResources().getDisplayMetrics()
77 );
78 scrollXScale = px;
79 scrollYScale = px;
80 }
81
82 nhandle = onCreateView(this);
83 imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
84 setFocusable(true);
85 setFocusableInTouchMode(true);
86 focusCallback = new View.OnFocusChangeListener() {
87 @Override public void onFocusChange(View v, boolean focus) {
88 GioView.this.onFocusChange(nhandle, focus);
89 }
90 };
91 setOnFocusChangeListener(focusCallback);
92 surfCallbacks = new SurfaceHolder.Callback() {
93 @Override public void surfaceCreated(SurfaceHolder holder) {
94 // Ignore; surfaceChanged is guaranteed to be called immediately after this.
95 }
96 @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
97 onSurfaceChanged(nhandle, getHolder().getSurface());
98 }
99 @Override public void surfaceDestroyed(SurfaceHolder holder) {
100 onSurfaceDestroyed(nhandle);
101 }
102 };
103 getHolder().addCallback(surfCallbacks);
104 }
105
106 @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
107 onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), event.getEventTime());
108 return false;
109 }
110
111 @Override public boolean onGenericMotionEvent(MotionEvent event) {
112 dispatchMotionEvent(event);
113 return true;
114 }
115
116 @Override public boolean onTouchEvent(MotionEvent event) {
117 // Ask for unbuffered events. Flutter and Chrome do it
118 // so assume it's good for us as well.
119 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
120 requestUnbufferedDispatch(event);
121 }
122
123 dispatchMotionEvent(event);
124 return true;
125 }
126
127 private void setCursor(int id) {
128 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
129 return;
130 }
131 PointerIcon pointerIcon = PointerIcon.getSystemIcon(getContext(), id);
132 setPointerIcon(pointerIcon);
133 }
134
135 private void dispatchMotionEvent(MotionEvent event) {
136 for (int j = 0; j < event.getHistorySize(); j++) {
137 long time = event.getHistoricalEventTime(j);
138 for (int i = 0; i < event.getPointerCount(); i++) {
139 onTouchEvent(
140 nhandle,
141 event.ACTION_MOVE,
142 event.getPointerId(i),
143 event.getToolType(i),
144 event.getHistoricalX(i, j),
145 event.getHistoricalY(i, j),
146 scrollXScale*event.getHistoricalAxisValue(MotionEvent.AXIS_HSCROLL, i, j),
147 scrollYScale*event.getHistoricalAxisValue(MotionEvent.AXIS_VSCROLL, i, j),
148 event.getButtonState(),
149 time);
150 }
151 }
152 int act = event.getActionMasked();
153 int idx = event.getActionIndex();
154 for (int i = 0; i < event.getPointerCount(); i++) {
155 int pact = event.ACTION_MOVE;
156 if (i == idx) {
157 pact = act;
158 }
159 onTouchEvent(
160 nhandle,
161 pact,
162 event.getPointerId(i),
163 event.getToolType(i),
164 event.getX(i), event.getY(i),
165 scrollXScale*event.getAxisValue(MotionEvent.AXIS_HSCROLL, i),
166 scrollYScale*event.getAxisValue(MotionEvent.AXIS_VSCROLL, i),
167 event.getButtonState(),
168 event.getEventTime());
169 }
170 }
171
172 @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
173 return new InputConnection(this);
174 }
175
176 void showTextInput() {
177 GioView.this.requestFocus();
178 imm.showSoftInput(GioView.this, 0);
179 }
180
181 void hideTextInput() {
182 imm.hideSoftInputFromWindow(getWindowToken(), 0);
183 }
184
185 @Override protected boolean fitSystemWindows(Rect insets) {
186 onWindowInsets(nhandle, insets.top, insets.right, insets.bottom, insets.left);
187 return true;
188 }
189
190 void postFrameCallback() {
191 Choreographer.getInstance().removeFrameCallback(this);
192 Choreographer.getInstance().postFrameCallback(this);
193 }
194
195 @Override public void doFrame(long nanos) {
196 onFrameCallback(nhandle, nanos);
197 }
198
199 int getDensity() {
200 return getResources().getDisplayMetrics().densityDpi;
201 }
202
203 float getFontScale() {
204 return getResources().getConfiguration().fontScale;
205 }
206
207 void start() {
208 onStartView(nhandle);
209 }
210
211 void stop() {
212 onStopView(nhandle);
213 }
214
215 void destroy() {
216 setOnFocusChangeListener(null);
217 getHolder().removeCallback(surfCallbacks);
218 onDestroyView(nhandle);
219 nhandle = 0;
220 }
221
222 void configurationChanged() {
223 onConfigurationChanged(nhandle);
224 }
225
226 void lowMemory() {
227 onLowMemory();
228 }
229
230 boolean backPressed() {
231 return onBack(nhandle);
232 }
233
234 static private native long onCreateView(GioView view);
235 static private native void onDestroyView(long handle);
236 static private native void onStartView(long handle);
237 static private native void onStopView(long handle);
238 static private native void onSurfaceDestroyed(long handle);
239 static private native void onSurfaceChanged(long handle, Surface surface);
240 static private native void onConfigurationChanged(long handle);
241 static private native void onWindowInsets(long handle, int top, int right, int bottom, int left);
242 static private native void onLowMemory();
243 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);
244 static private native void onKeyEvent(long handle, int code, int character, long time);
245 static private native void onFrameCallback(long handle, long nanos);
246 static private native boolean onBack(long handle);
247 static private native void onFocusChange(long handle, boolean focus);
248
249 private static class InputConnection extends BaseInputConnection {
250 private final Editable editable;
251
252 InputConnection(View view) {
253 // Passing false enables "dummy mode", where the BaseInputConnection
254 // attempts to convert IME operations to key events.
255 super(view, false);
256 editable = Editable.Factory.getInstance().newEditable("");
257 }
258
259 @Override public Editable getEditable() {
260 return editable;
261 }
262 }
263 }
264