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