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