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  }