package org.golang.app; import android.app.Activity; import android.app.NativeActivity; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Rect; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; import android.util.Log; import android.view.Gravity; import android.view.KeyCharacterMap; import android.view.View; import android.view.WindowInsets; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.view.KeyEvent; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; public class GoNativeActivity extends NativeActivity { private static GoNativeActivity goNativeActivity; private static final int FILE_OPEN_CODE = 1; private static final int FILE_SAVE_CODE = 2; private static final int DEFAULT_INPUT_TYPE = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS; private static final int DEFAULT_KEYBOARD_CODE = 0; private static final int SINGLELINE_KEYBOARD_CODE = 1; private static final int NUMBER_KEYBOARD_CODE = 2; private static final int PASSWORD_KEYBOARD_CODE = 3; private native void filePickerReturned(String str); private native void insetsChanged(int top, int bottom, int left, int right); private native void keyboardTyped(String str); private native void keyboardDelete(); private native void setDarkMode(boolean dark); private EditText mTextEdit; private boolean ignoreKey = false; public GoNativeActivity() { super(); goNativeActivity = this; } String getTmpdir() { return getCacheDir().getAbsolutePath(); } void updateLayout() { try { WindowInsets insets = getWindow().getDecorView().getRootWindowInsets(); if (insets == null) { return; } insetsChanged(insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetBottom(), insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetRight()); } catch (java.lang.NoSuchMethodError e) { Rect insets = new Rect(); getWindow().getDecorView().getWindowVisibleDisplayFrame(insets); View view = findViewById(android.R.id.content).getRootView(); insetsChanged(insets.top, view.getHeight() - insets.height() - insets.top, insets.left, view.getWidth() - insets.width() - insets.left); } } static void showKeyboard(int keyboardType) { goNativeActivity.doShowKeyboard(keyboardType); } void doShowKeyboard(final int keyboardType) { runOnUiThread(new Runnable() { @Override public void run() { int imeOptions = EditorInfo.IME_FLAG_NO_ENTER_ACTION; int inputType = DEFAULT_INPUT_TYPE; switch (keyboardType) { case DEFAULT_KEYBOARD_CODE: imeOptions = EditorInfo.IME_FLAG_NO_ENTER_ACTION; break; case SINGLELINE_KEYBOARD_CODE: imeOptions = EditorInfo.IME_ACTION_DONE; break; case NUMBER_KEYBOARD_CODE: imeOptions = EditorInfo.IME_ACTION_DONE; inputType |= InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL; break; case PASSWORD_KEYBOARD_CODE: imeOptions = EditorInfo.IME_ACTION_DONE; inputType |= InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; default: Log.e("Fyne", "unknown keyboard type, use default"); } mTextEdit.setImeOptions(imeOptions); mTextEdit.setInputType(inputType); mTextEdit.setOnEditorActionListener(new OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_DONE) { keyboardTyped("\n"); } return false; } }); // always place one character so all keyboards can send backspace ignoreKey = true; mTextEdit.setText("0"); mTextEdit.setSelection(mTextEdit.getText().length()); ignoreKey = false; mTextEdit.setVisibility(View.VISIBLE); mTextEdit.bringToFront(); mTextEdit.requestFocus(); InputMethodManager m = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); m.showSoftInput(mTextEdit, 0); } }); } static void hideKeyboard() { goNativeActivity.doHideKeyboard(); } void doHideKeyboard() { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); View view = findViewById(android.R.id.content).getRootView(); imm.hideSoftInputFromWindow(view.getWindowToken(), 0); runOnUiThread(new Runnable() { @Override public void run() { mTextEdit.setVisibility(View.GONE); } }); } static void showFileOpen(String mimes) { goNativeActivity.doShowFileOpen(mimes); } void doShowFileOpen(String mimes) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); if ("application/x-directory".equals(mimes) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); // ask for a directory picker if OS supports it intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } else if (mimes.contains("|") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { intent.setType("*/*"); intent.putExtra(Intent.EXTRA_MIME_TYPES, mimes.split("\\|")); intent.addCategory(Intent.CATEGORY_OPENABLE); } else { intent.setType(mimes); intent.addCategory(Intent.CATEGORY_OPENABLE); } startActivityForResult(Intent.createChooser(intent, "Open File"), FILE_OPEN_CODE); } static void showFileSave(String mimes, String filename) { goNativeActivity.doShowFileSave(mimes, filename); } void doShowFileSave(String mimes, String filename) { Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); if (mimes.contains("|") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { intent.setType("*/*"); intent.putExtra(Intent.EXTRA_MIME_TYPES, mimes.split("\\|")); } else { intent.setType(mimes); } intent.putExtra(Intent.EXTRA_TITLE, filename); intent.addCategory(Intent.CATEGORY_OPENABLE); startActivityForResult(Intent.createChooser(intent, "Save File"), FILE_SAVE_CODE); } static int getRune(int deviceId, int keyCode, int metaState) { try { int rune = KeyCharacterMap.load(deviceId).get(keyCode, metaState); if (rune == 0) { return -1; } return rune; } catch (KeyCharacterMap.UnavailableException e) { return -1; } catch (Exception e) { Log.e("Fyne", "exception reading KeyCharacterMap", e); return -1; } } private void load() { // Interestingly, NativeActivity uses a different method // to find native code to execute, avoiding // System.loadLibrary. The result is Java methods // implemented in C with JNIEXPORT (and JNI_OnLoad) are not // available unless an explicit call to System.loadLibrary // is done. So we do it here, borrowing the name of the // library from the same AndroidManifest.xml metadata used // by NativeActivity. try { ActivityInfo ai = getPackageManager().getActivityInfo( getIntent().getComponent(), PackageManager.GET_META_DATA); if (ai.metaData == null) { Log.e("Fyne", "loadLibrary: no manifest metadata found"); return; } String libName = ai.metaData.getString("android.app.lib_name"); System.loadLibrary(libName); } catch (Exception e) { Log.e("Fyne", "loadLibrary failed", e); } } @Override public void onCreate(Bundle savedInstanceState) { load(); super.onCreate(savedInstanceState); setupEntry(); updateTheme(getResources().getConfiguration()); View view = findViewById(android.R.id.content).getRootView(); view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { public void onLayoutChange (View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { GoNativeActivity.this.updateLayout(); } }); } private void setupEntry() { runOnUiThread(new Runnable() { @Override public void run() { mTextEdit = new EditText(goNativeActivity); mTextEdit.setVisibility(View.GONE); mTextEdit.setInputType(DEFAULT_INPUT_TYPE); FrameLayout.LayoutParams mEditTextLayoutParams = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT); mTextEdit.setLayoutParams(mEditTextLayoutParams); addContentView(mTextEdit, mEditTextLayoutParams); // always place one character so all keyboards can send backspace mTextEdit.setText("0"); mTextEdit.setSelection(mTextEdit.getText().length()); mTextEdit.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (ignoreKey) { return; } if (count > 0) { keyboardTyped(s.subSequence(start,start+count).toString()); } } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { if (ignoreKey) { return; } if (count > 0) { for (int i = 0; i < count; i++) { // send a backspace keyboardDelete(); } } } @Override public void afterTextChanged(Editable s) { // always place one character so all keyboards can send backspace if (s.length() < 1) { ignoreKey = true; mTextEdit.setText("0"); mTextEdit.setSelection(mTextEdit.getText().length()); ignoreKey = false; return; } } }); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // unhandled request if (requestCode != FILE_OPEN_CODE && requestCode != FILE_SAVE_CODE) { return; } // dialog was cancelled if (resultCode != Activity.RESULT_OK) { filePickerReturned(""); return; } Uri uri = data.getData(); filePickerReturned(uri.toString()); } @Override public void onConfigurationChanged(Configuration config) { super.onConfigurationChanged(config); updateTheme(config); } protected void updateTheme(Configuration config) { boolean dark = (config.uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; setDarkMode(dark); } }