github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/app/internal/wm/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.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.Window;
    33  import android.view.WindowInsetsController;
    34  import android.view.WindowManager;
    35  import android.view.inputmethod.BaseInputConnection;
    36  import android.view.inputmethod.InputConnection;
    37  import android.view.inputmethod.InputMethodManager;
    38  import android.view.inputmethod.EditorInfo;
    39  import android.text.InputType;
    40  
    41  import java.io.UnsupportedEncodingException;
    42  
    43  public final class GioView extends SurfaceView implements Choreographer.FrameCallback {
    44  	private static boolean jniLoaded;
    45  
    46  	private final SurfaceHolder.Callback surfCallbacks;
    47  	private final View.OnFocusChangeListener focusCallback;
    48  	private final InputMethodManager imm;
    49  	private final float scrollXScale;
    50  	private final float scrollYScale;
    51  	private int keyboardHint;
    52  
    53  	private long nhandle;
    54  
    55  	public GioView(Context context) {
    56  		this(context, null);
    57  	}
    58  
    59  	public GioView(Context context, AttributeSet attrs) {
    60  		super(context, attrs);
    61  		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    62  			setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
    63  		}
    64  		setLayoutParams(new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT));
    65  
    66  		// Late initialization of the Go runtime to wait for a valid context.
    67  		Gio.init(context.getApplicationContext());
    68  
    69  		// Set background color to transparent to avoid a flickering
    70  		// issue on ChromeOS.
    71  		setBackgroundColor(Color.argb(0, 0, 0, 0));
    72  
    73  		ViewConfiguration conf = ViewConfiguration.get(context);
    74  		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    75  			scrollXScale = conf.getScaledHorizontalScrollFactor();
    76  			scrollYScale = conf.getScaledVerticalScrollFactor();
    77  
    78  			// The platform focus highlight is not aware of Gio's widgets.
    79  			setDefaultFocusHighlightEnabled(false);
    80  		} else {
    81  			float listItemHeight = 48; // dp
    82  			float px = TypedValue.applyDimension(
    83  				TypedValue.COMPLEX_UNIT_DIP,
    84  				listItemHeight,
    85  				getResources().getDisplayMetrics()
    86  			);
    87  			scrollXScale = px;
    88  			scrollYScale = px;
    89  		}
    90  
    91  		nhandle = onCreateView(this);
    92  		imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
    93  		setFocusable(true);
    94  		setFocusableInTouchMode(true);
    95  		focusCallback = new View.OnFocusChangeListener() {
    96  			@Override public void onFocusChange(View v, boolean focus) {
    97  				GioView.this.onFocusChange(nhandle, focus);
    98  			}
    99  		};
   100  		setOnFocusChangeListener(focusCallback);
   101  		surfCallbacks = new SurfaceHolder.Callback() {
   102  			@Override public void surfaceCreated(SurfaceHolder holder) {
   103  				// Ignore; surfaceChanged is guaranteed to be called immediately after this.
   104  			}
   105  			@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
   106  				onSurfaceChanged(nhandle, getHolder().getSurface());
   107  			}
   108  			@Override public void surfaceDestroyed(SurfaceHolder holder) {
   109  				onSurfaceDestroyed(nhandle);
   110  			}
   111  		};
   112  		getHolder().addCallback(surfCallbacks);
   113  	}
   114  
   115  	@Override public boolean onKeyDown(int keyCode, KeyEvent event) {
   116  		onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), event.getEventTime());
   117  		return false;
   118  	}
   119  
   120  	@Override public boolean onGenericMotionEvent(MotionEvent event) {
   121  		dispatchMotionEvent(event);
   122  		return true;
   123  	}
   124  
   125  	@Override public boolean onTouchEvent(MotionEvent event) {
   126  		// Ask for unbuffered events. Flutter and Chrome do it
   127  		// so assume it's good for us as well.
   128  		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
   129  			requestUnbufferedDispatch(event);
   130  		}
   131  
   132  		dispatchMotionEvent(event);
   133  		return true;
   134  	}
   135  
   136  	private void setCursor(int id) {
   137  		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
   138  			return;
   139  		}
   140  		PointerIcon pointerIcon = PointerIcon.getSystemIcon(getContext(), id);
   141  		setPointerIcon(pointerIcon);
   142  	}
   143  
   144      private void setOrientation(int id, int fallback) {
   145          if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
   146              id = fallback;
   147          }
   148          ((Activity) this.getContext()).setRequestedOrientation(id);
   149      }
   150  
   151      private void setFullscreen(boolean enabled) {
   152          int flags = this.getSystemUiVisibility();
   153          if (enabled) {
   154             flags |= SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
   155             flags |= SYSTEM_UI_FLAG_HIDE_NAVIGATION;
   156             flags |= SYSTEM_UI_FLAG_FULLSCREEN;
   157             flags |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
   158          } else {
   159             flags &= ~SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
   160             flags &= ~SYSTEM_UI_FLAG_HIDE_NAVIGATION;
   161             flags &= ~SYSTEM_UI_FLAG_FULLSCREEN;
   162             flags &= ~SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
   163          }
   164          this.setSystemUiVisibility(flags);
   165      }
   166  
   167      private enum Bar {
   168          NAVIGATION,
   169          STATUS,
   170      }
   171  
   172      private void setBarColor(Bar t, int color, int luminance) {
   173          if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
   174              return;
   175          }
   176  
   177          Window window = ((Activity) this.getContext()).getWindow();
   178  
   179          int insetsMask;
   180          int viewMask;
   181  
   182          switch (t) {
   183          case STATUS:
   184              insetsMask = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
   185              viewMask = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
   186              window.setStatusBarColor(color);
   187              break;
   188          case NAVIGATION:
   189              insetsMask = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
   190              viewMask = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
   191              window.setNavigationBarColor(color);
   192              break;
   193          default:
   194              throw new RuntimeException("invalid bar type");
   195          }
   196  
   197          if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
   198              return;
   199          }
   200  
   201          if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
   202              int flags = this.getSystemUiVisibility();
   203              if (luminance > 128) {
   204                 flags |=  viewMask;
   205              } else {
   206                 flags &= ~viewMask;
   207              }
   208              this.setSystemUiVisibility(flags);
   209              return;
   210          }
   211  
   212          WindowInsetsController insetsController = window.getInsetsController();
   213          if (insetsController == null) {
   214              return;
   215          }
   216          if (luminance > 128) {
   217              insetsController.setSystemBarsAppearance(insetsMask, insetsMask);
   218          } else {
   219              insetsController.setSystemBarsAppearance(0, insetsMask);
   220          }
   221      }
   222  
   223      private void setStatusColor(int color, int luminance) {
   224          this.setBarColor(Bar.STATUS, color, luminance);
   225      }
   226  
   227      private void setNavigationColor(int color, int luminance) {
   228          this.setBarColor(Bar.NAVIGATION, color, luminance);
   229      }
   230  
   231  	private void dispatchMotionEvent(MotionEvent event) {
   232  		for (int j = 0; j < event.getHistorySize(); j++) {
   233  			long time = event.getHistoricalEventTime(j);
   234  			for (int i = 0; i < event.getPointerCount(); i++) {
   235  				onTouchEvent(
   236  						nhandle,
   237  						event.ACTION_MOVE,
   238  						event.getPointerId(i),
   239  						event.getToolType(i),
   240  						event.getHistoricalX(i, j),
   241  						event.getHistoricalY(i, j),
   242  						scrollXScale*event.getHistoricalAxisValue(MotionEvent.AXIS_HSCROLL, i, j),
   243  						scrollYScale*event.getHistoricalAxisValue(MotionEvent.AXIS_VSCROLL, i, j),
   244  						event.getButtonState(),
   245  						time);
   246  			}
   247  		}
   248  		int act = event.getActionMasked();
   249  		int idx = event.getActionIndex();
   250  		for (int i = 0; i < event.getPointerCount(); i++) {
   251  			int pact = event.ACTION_MOVE;
   252  			if (i == idx) {
   253  				pact = act;
   254  			}
   255  			onTouchEvent(
   256  					nhandle,
   257  					pact,
   258  					event.getPointerId(i),
   259  					event.getToolType(i),
   260  					event.getX(i), event.getY(i),
   261  					scrollXScale*event.getAxisValue(MotionEvent.AXIS_HSCROLL, i),
   262  					scrollYScale*event.getAxisValue(MotionEvent.AXIS_VSCROLL, i),
   263  					event.getButtonState(),
   264  					event.getEventTime());
   265  		}
   266  	}
   267  
   268  	@Override public InputConnection onCreateInputConnection(EditorInfo editor) {
   269  		// The TYPE_TEXT_FLAG_NO_SUGGESTIONS and TYPE_TEXT_VARIATION_VISIBLE_PASSWORD are used to fix the
   270  		// Samsung keyboard compatibility, forcing to disable the suggests/auto-complete. gio#116.
   271  		editor.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD | this.keyboardHint;
   272  		editor.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
   273  		return new InputConnection(this);
   274  	}
   275  
   276  	void setInputHint(int hint) {
   277  	    if (hint == this.keyboardHint) {
   278  	        return;
   279  	    }
   280  	    this.keyboardHint = hint;
   281  	    imm.restartInput(this);
   282  	}
   283  
   284  	void showTextInput() {
   285  		GioView.this.requestFocus();
   286  		imm.showSoftInput(GioView.this, 0);
   287  	}
   288  
   289  	void hideTextInput() {
   290  		imm.hideSoftInputFromWindow(getWindowToken(), 0);
   291  	}
   292  
   293  	@Override protected boolean fitSystemWindows(Rect insets) {
   294  		onWindowInsets(nhandle, insets.top, insets.right, insets.bottom, insets.left);
   295  		return true;
   296  	}
   297  
   298  	void postFrameCallback() {
   299  		Choreographer.getInstance().removeFrameCallback(this);
   300  		Choreographer.getInstance().postFrameCallback(this);
   301  	}
   302  
   303  	@Override public void doFrame(long nanos) {
   304  		onFrameCallback(nhandle, nanos);
   305  	}
   306  
   307  	int getDensity() {
   308  		return getResources().getDisplayMetrics().densityDpi;
   309  	}
   310  
   311  	float getFontScale() {
   312  		return getResources().getConfiguration().fontScale;
   313  	}
   314  
   315  	public void start() {
   316  		onStartView(nhandle);
   317  	}
   318  
   319  	public void stop() {
   320  		onStopView(nhandle);
   321  	}
   322  
   323  	public void destroy() {
   324  		setOnFocusChangeListener(null);
   325  		getHolder().removeCallback(surfCallbacks);
   326  		onDestroyView(nhandle);
   327  		nhandle = 0;
   328  	}
   329  
   330  	public void configurationChanged() {
   331  		onConfigurationChanged(nhandle);
   332  	}
   333  
   334  	public boolean backPressed() {
   335  		return onBack(nhandle);
   336  	}
   337  
   338  	static private native long onCreateView(GioView view);
   339  	static private native void onDestroyView(long handle);
   340  	static private native void onStartView(long handle);
   341  	static private native void onStopView(long handle);
   342  	static private native void onSurfaceDestroyed(long handle);
   343  	static private native void onSurfaceChanged(long handle, Surface surface);
   344  	static private native void onConfigurationChanged(long handle);
   345  	static private native void onWindowInsets(long handle, int top, int right, int bottom, int left);
   346  	static public native void onLowMemory();
   347  	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);
   348  	static private native void onKeyEvent(long handle, int code, int character, long time);
   349  	static private native void onFrameCallback(long handle, long nanos);
   350  	static private native boolean onBack(long handle);
   351  	static private native void onFocusChange(long handle, boolean focus);
   352  
   353  	private static class InputConnection extends BaseInputConnection {
   354  		private final Editable editable;
   355  
   356  		InputConnection(View view) {
   357  			// Passing false enables "dummy mode", where the BaseInputConnection
   358  			// attempts to convert IME operations to key events.
   359  			super(view, false);
   360  			editable = Editable.Factory.getInstance().newEditable("");
   361  		}
   362  
   363  		@Override public Editable getEditable() {
   364  			return editable;
   365  		}
   366  	}
   367  }