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 }