gioui.org/ui@v0.0.0-20190926171558-ce74bc0cbaea/app/GioView.java (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package org.gioui; 4 5 import android.content.Context; 6 import android.graphics.Rect; 7 import android.os.Build; 8 import android.os.Handler; 9 import android.util.AttributeSet; 10 import android.text.Editable; 11 import android.view.Choreographer; 12 import android.view.KeyCharacterMap; 13 import android.view.KeyEvent; 14 import android.view.MotionEvent; 15 import android.view.View; 16 import android.view.WindowInsets; 17 import android.view.Surface; 18 import android.view.SurfaceView; 19 import android.view.SurfaceHolder; 20 import android.view.inputmethod.BaseInputConnection; 21 import android.view.inputmethod.InputConnection; 22 import android.view.inputmethod.InputMethodManager; 23 import android.view.inputmethod.EditorInfo; 24 25 import java.io.UnsupportedEncodingException; 26 27 public class GioView extends SurfaceView implements Choreographer.FrameCallback { 28 private final static Object initLock = new Object(); 29 private static boolean jniLoaded; 30 31 private final SurfaceHolder.Callback callbacks; 32 private final InputMethodManager imm; 33 private final Handler handler; 34 private long nhandle; 35 36 private static synchronized void initialize(Context appCtx) { 37 synchronized (initLock) { 38 if (jniLoaded) { 39 return; 40 } 41 String dataDir = appCtx.getFilesDir().getAbsolutePath(); 42 byte[] dataDirUTF8; 43 try { 44 dataDirUTF8 = dataDir.getBytes("UTF-8"); 45 } catch (UnsupportedEncodingException e) { 46 throw new RuntimeException(e); 47 } 48 System.loadLibrary("gio"); 49 runGoMain(dataDirUTF8); 50 jniLoaded = true; 51 } 52 } 53 54 public GioView(Context context) { 55 this(context, null); 56 } 57 58 public GioView(Context context, AttributeSet attrs) { 59 super(context, attrs); 60 // Late initialization of the Go runtime to wait for a valid context. 61 initialize(context.getApplicationContext()); 62 63 nhandle = onCreateView(this); 64 handler = new Handler(); 65 imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE); 66 setFocusable(true); 67 setFocusableInTouchMode(true); 68 setOnFocusChangeListener(new View.OnFocusChangeListener() { 69 @Override public void onFocusChange(View v, boolean focus) { 70 GioView.this.onFocusChange(nhandle, focus); 71 } 72 }); 73 callbacks = new SurfaceHolder.Callback() { 74 @Override public void surfaceCreated(SurfaceHolder holder) { 75 // Ignore; surfaceChanged is guaranteed to be called immediately after this. 76 } 77 @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 78 onSurfaceChanged(nhandle, getHolder().getSurface()); 79 } 80 @Override public void surfaceDestroyed(SurfaceHolder holder) { 81 onSurfaceDestroyed(nhandle); 82 } 83 }; 84 getHolder().addCallback(callbacks); 85 } 86 87 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { 88 onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), event.getEventTime()); 89 return false; 90 } 91 92 @Override public boolean onTouchEvent(MotionEvent event) { 93 // Ask for unbuffered events. Flutter and Chrome does it 94 // so I assume its good for us as well. 95 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 96 requestUnbufferedDispatch(event); 97 } 98 99 for (int j = 0; j < event.getHistorySize(); j++) { 100 long time = event.getHistoricalEventTime(j); 101 for (int i = 0; i < event.getPointerCount(); i++) { 102 onTouchEvent( 103 nhandle, 104 event.ACTION_MOVE, 105 event.getPointerId(i), 106 event.getToolType(i), 107 event.getHistoricalX(i, j), 108 event.getHistoricalY(i, j), 109 time); 110 } 111 } 112 int act = event.getActionMasked(); 113 int idx = event.getActionIndex(); 114 for (int i = 0; i < event.getPointerCount(); i++) { 115 int pact = event.ACTION_MOVE; 116 if (i == idx) { 117 pact = act; 118 } 119 onTouchEvent( 120 nhandle, 121 act, 122 event.getPointerId(i), 123 event.getToolType(i), 124 event.getX(i), 125 event.getY(i), 126 event.getEventTime()); 127 } 128 return true; 129 } 130 131 @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 132 return new InputConnection(this); 133 } 134 135 void showTextInput() { 136 post(new Runnable() { 137 @Override public void run() { 138 GioView.this.requestFocus(); 139 imm.showSoftInput(GioView.this, 0); 140 } 141 }); 142 } 143 144 void hideTextInput() { 145 post(new Runnable() { 146 @Override public void run() { 147 imm.hideSoftInputFromWindow(getWindowToken(), 0); 148 } 149 }); 150 } 151 152 void postFrameCallbackOnMainThread() { 153 handler.post(new Runnable() { 154 @Override public void run() { 155 postFrameCallback(); 156 } 157 }); 158 } 159 160 @Override protected boolean fitSystemWindows(Rect insets) { 161 onWindowInsets(nhandle, insets.top, insets.right, insets.bottom, insets.left); 162 return true; 163 } 164 165 void postFrameCallback() { 166 Choreographer.getInstance().removeFrameCallback(this); 167 Choreographer.getInstance().postFrameCallback(this); 168 } 169 170 @Override public void doFrame(long nanos) { 171 onFrameCallback(nhandle, nanos); 172 } 173 174 int getDensity() { 175 return getResources().getDisplayMetrics().densityDpi; 176 } 177 178 float getFontScale() { 179 return getResources().getConfiguration().fontScale; 180 } 181 182 void start() { 183 onStartView(nhandle); 184 } 185 186 void stop() { 187 onStopView(nhandle); 188 } 189 190 void destroy() { 191 getHolder().removeCallback(callbacks); 192 onDestroyView(nhandle); 193 nhandle = 0; 194 } 195 196 void configurationChanged() { 197 onConfigurationChanged(nhandle); 198 } 199 200 void lowMemory() { 201 onLowMemory(); 202 } 203 204 boolean backPressed() { 205 return onBack(nhandle); 206 } 207 208 static private native long onCreateView(GioView view); 209 static private native void onDestroyView(long handle); 210 static private native void onStartView(long handle); 211 static private native void onStopView(long handle); 212 static private native void onSurfaceDestroyed(long handle); 213 static private native void onSurfaceChanged(long handle, Surface surface); 214 static private native void onConfigurationChanged(long handle); 215 static private native void onWindowInsets(long handle, int top, int right, int bottom, int left); 216 static private native void onLowMemory(); 217 static private native void onTouchEvent(long handle, int action, int pointerID, int tool, float x, float y, long time); 218 static private native void onKeyEvent(long handle, int code, int character, long time); 219 static private native void onFrameCallback(long handle, long nanos); 220 static private native boolean onBack(long handle); 221 static private native void onFocusChange(long handle, boolean focus); 222 static private native void runGoMain(byte[] dataDir); 223 224 private static class InputConnection extends BaseInputConnection { 225 private final Editable editable; 226 227 InputConnection(View view) { 228 // Passing false enables "dummy mode", where the BaseInputConnection 229 // attempts to convert IME operations to key events. 230 super(view, false); 231 editable = Editable.Factory.getInstance().newEditable(""); 232 } 233 234 @Override public Editable getEditable() { 235 return editable; 236 } 237 } 238 }