GoNativeActivity.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. package org.golang.app;
  2. import android.app.Activity;
  3. import android.app.NativeActivity;
  4. import android.content.Context;
  5. import android.content.Intent;
  6. import android.content.pm.ActivityInfo;
  7. import android.content.pm.PackageManager;
  8. import android.content.res.Configuration;
  9. import android.graphics.Rect;
  10. import android.net.Uri;
  11. import android.os.Build;
  12. import android.os.Bundle;
  13. import android.text.Editable;
  14. import android.text.InputType;
  15. import android.text.TextWatcher;
  16. import android.util.Log;
  17. import android.view.Gravity;
  18. import android.view.KeyCharacterMap;
  19. import android.view.View;
  20. import android.view.WindowInsets;
  21. import android.view.inputmethod.EditorInfo;
  22. import android.view.inputmethod.InputMethodManager;
  23. import android.view.KeyEvent;
  24. import android.widget.EditText;
  25. import android.widget.FrameLayout;
  26. import android.widget.TextView;
  27. import android.widget.TextView.OnEditorActionListener;
  28. public class GoNativeActivity extends NativeActivity {
  29. private static GoNativeActivity goNativeActivity;
  30. private static final int FILE_OPEN_CODE = 1;
  31. private static final int FILE_SAVE_CODE = 2;
  32. private static final int DEFAULT_INPUT_TYPE = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
  33. private static final int DEFAULT_KEYBOARD_CODE = 0;
  34. private static final int SINGLELINE_KEYBOARD_CODE = 1;
  35. private static final int NUMBER_KEYBOARD_CODE = 2;
  36. private static final int PASSWORD_KEYBOARD_CODE = 3;
  37. private native void filePickerReturned(String str);
  38. private native void insetsChanged(int top, int bottom, int left, int right);
  39. private native void keyboardTyped(String str);
  40. private native void keyboardDelete();
  41. private native void setDarkMode(boolean dark);
  42. private EditText mTextEdit;
  43. private boolean ignoreKey = false;
  44. public GoNativeActivity() {
  45. super();
  46. goNativeActivity = this;
  47. }
  48. String getTmpdir() {
  49. return getCacheDir().getAbsolutePath();
  50. }
  51. void updateLayout() {
  52. try {
  53. WindowInsets insets = getWindow().getDecorView().getRootWindowInsets();
  54. if (insets == null) {
  55. return;
  56. }
  57. insetsChanged(insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetBottom(),
  58. insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetRight());
  59. } catch (java.lang.NoSuchMethodError e) {
  60. Rect insets = new Rect();
  61. getWindow().getDecorView().getWindowVisibleDisplayFrame(insets);
  62. View view = findViewById(android.R.id.content).getRootView();
  63. insetsChanged(insets.top, view.getHeight() - insets.height() - insets.top,
  64. insets.left, view.getWidth() - insets.width() - insets.left);
  65. }
  66. }
  67. static void showKeyboard(int keyboardType) {
  68. goNativeActivity.doShowKeyboard(keyboardType);
  69. }
  70. void doShowKeyboard(final int keyboardType) {
  71. runOnUiThread(new Runnable() {
  72. @Override
  73. public void run() {
  74. int imeOptions = EditorInfo.IME_FLAG_NO_ENTER_ACTION;
  75. int inputType = DEFAULT_INPUT_TYPE;
  76. switch (keyboardType) {
  77. case DEFAULT_KEYBOARD_CODE:
  78. imeOptions = EditorInfo.IME_FLAG_NO_ENTER_ACTION;
  79. break;
  80. case SINGLELINE_KEYBOARD_CODE:
  81. imeOptions = EditorInfo.IME_ACTION_DONE;
  82. break;
  83. case NUMBER_KEYBOARD_CODE:
  84. imeOptions = EditorInfo.IME_ACTION_DONE;
  85. inputType |= InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL;
  86. break;
  87. case PASSWORD_KEYBOARD_CODE:
  88. imeOptions = EditorInfo.IME_ACTION_DONE;
  89. inputType |= InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
  90. default:
  91. Log.e("Fyne", "unknown keyboard type, use default");
  92. }
  93. mTextEdit.setImeOptions(imeOptions);
  94. mTextEdit.setInputType(inputType);
  95. mTextEdit.setOnEditorActionListener(new OnEditorActionListener() {
  96. @Override
  97. public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
  98. if (actionId == EditorInfo.IME_ACTION_DONE) {
  99. keyboardTyped("\n");
  100. }
  101. return false;
  102. }
  103. });
  104. // always place one character so all keyboards can send backspace
  105. ignoreKey = true;
  106. mTextEdit.setText("0");
  107. mTextEdit.setSelection(mTextEdit.getText().length());
  108. ignoreKey = false;
  109. mTextEdit.setVisibility(View.VISIBLE);
  110. mTextEdit.bringToFront();
  111. mTextEdit.requestFocus();
  112. InputMethodManager m = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
  113. m.showSoftInput(mTextEdit, 0);
  114. }
  115. });
  116. }
  117. static void hideKeyboard() {
  118. goNativeActivity.doHideKeyboard();
  119. }
  120. void doHideKeyboard() {
  121. InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
  122. View view = findViewById(android.R.id.content).getRootView();
  123. imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
  124. runOnUiThread(new Runnable() {
  125. @Override
  126. public void run() {
  127. mTextEdit.setVisibility(View.GONE);
  128. }
  129. });
  130. }
  131. static void showFileOpen(String mimes) {
  132. goNativeActivity.doShowFileOpen(mimes);
  133. }
  134. void doShowFileOpen(String mimes) {
  135. Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
  136. if ("application/x-directory".equals(mimes) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  137. intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); // ask for a directory picker if OS supports it
  138. intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
  139. } else if (mimes.contains("|") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  140. intent.setType("*/*");
  141. intent.putExtra(Intent.EXTRA_MIME_TYPES, mimes.split("\\|"));
  142. intent.addCategory(Intent.CATEGORY_OPENABLE);
  143. } else {
  144. intent.setType(mimes);
  145. intent.addCategory(Intent.CATEGORY_OPENABLE);
  146. }
  147. startActivityForResult(Intent.createChooser(intent, "Open File"), FILE_OPEN_CODE);
  148. }
  149. static void showFileSave(String mimes, String filename) {
  150. goNativeActivity.doShowFileSave(mimes, filename);
  151. }
  152. void doShowFileSave(String mimes, String filename) {
  153. Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
  154. if (mimes.contains("|") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  155. intent.setType("*/*");
  156. intent.putExtra(Intent.EXTRA_MIME_TYPES, mimes.split("\\|"));
  157. } else {
  158. intent.setType(mimes);
  159. }
  160. intent.putExtra(Intent.EXTRA_TITLE, filename);
  161. intent.addCategory(Intent.CATEGORY_OPENABLE);
  162. startActivityForResult(Intent.createChooser(intent, "Save File"), FILE_SAVE_CODE);
  163. }
  164. static int getRune(int deviceId, int keyCode, int metaState) {
  165. try {
  166. int rune = KeyCharacterMap.load(deviceId).get(keyCode, metaState);
  167. if (rune == 0) {
  168. return -1;
  169. }
  170. return rune;
  171. } catch (KeyCharacterMap.UnavailableException e) {
  172. return -1;
  173. } catch (Exception e) {
  174. Log.e("Fyne", "exception reading KeyCharacterMap", e);
  175. return -1;
  176. }
  177. }
  178. private void load() {
  179. // Interestingly, NativeActivity uses a different method
  180. // to find native code to execute, avoiding
  181. // System.loadLibrary. The result is Java methods
  182. // implemented in C with JNIEXPORT (and JNI_OnLoad) are not
  183. // available unless an explicit call to System.loadLibrary
  184. // is done. So we do it here, borrowing the name of the
  185. // library from the same AndroidManifest.xml metadata used
  186. // by NativeActivity.
  187. try {
  188. ActivityInfo ai = getPackageManager().getActivityInfo(
  189. getIntent().getComponent(), PackageManager.GET_META_DATA);
  190. if (ai.metaData == null) {
  191. Log.e("Fyne", "loadLibrary: no manifest metadata found");
  192. return;
  193. }
  194. String libName = ai.metaData.getString("android.app.lib_name");
  195. System.loadLibrary(libName);
  196. } catch (Exception e) {
  197. Log.e("Fyne", "loadLibrary failed", e);
  198. }
  199. }
  200. @Override
  201. public void onCreate(Bundle savedInstanceState) {
  202. load();
  203. super.onCreate(savedInstanceState);
  204. setupEntry();
  205. updateTheme(getResources().getConfiguration());
  206. View view = findViewById(android.R.id.content).getRootView();
  207. view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
  208. public void onLayoutChange (View v, int left, int top, int right, int bottom,
  209. int oldLeft, int oldTop, int oldRight, int oldBottom) {
  210. GoNativeActivity.this.updateLayout();
  211. }
  212. });
  213. }
  214. private void setupEntry() {
  215. runOnUiThread(new Runnable() {
  216. @Override
  217. public void run() {
  218. mTextEdit = new EditText(goNativeActivity);
  219. mTextEdit.setVisibility(View.GONE);
  220. mTextEdit.setInputType(DEFAULT_INPUT_TYPE);
  221. FrameLayout.LayoutParams mEditTextLayoutParams = new FrameLayout.LayoutParams(
  222. FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
  223. mTextEdit.setLayoutParams(mEditTextLayoutParams);
  224. addContentView(mTextEdit, mEditTextLayoutParams);
  225. // always place one character so all keyboards can send backspace
  226. mTextEdit.setText("0");
  227. mTextEdit.setSelection(mTextEdit.getText().length());
  228. mTextEdit.addTextChangedListener(new TextWatcher() {
  229. @Override
  230. public void onTextChanged(CharSequence s, int start, int before, int count) {
  231. if (ignoreKey) {
  232. return;
  233. }
  234. if (count > 0) {
  235. keyboardTyped(s.subSequence(start,start+count).toString());
  236. }
  237. }
  238. @Override
  239. public void beforeTextChanged(CharSequence s, int start, int count, int after) {
  240. if (ignoreKey) {
  241. return;
  242. }
  243. if (count > 0) {
  244. for (int i = 0; i < count; i++) {
  245. // send a backspace
  246. keyboardDelete();
  247. }
  248. }
  249. }
  250. @Override
  251. public void afterTextChanged(Editable s) {
  252. // always place one character so all keyboards can send backspace
  253. if (s.length() < 1) {
  254. ignoreKey = true;
  255. mTextEdit.setText("0");
  256. mTextEdit.setSelection(mTextEdit.getText().length());
  257. ignoreKey = false;
  258. return;
  259. }
  260. }
  261. });
  262. }
  263. });
  264. }
  265. @Override
  266. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  267. // unhandled request
  268. if (requestCode != FILE_OPEN_CODE && requestCode != FILE_SAVE_CODE) {
  269. return;
  270. }
  271. // dialog was cancelled
  272. if (resultCode != Activity.RESULT_OK) {
  273. filePickerReturned("");
  274. return;
  275. }
  276. Uri uri = data.getData();
  277. filePickerReturned(uri.toString());
  278. }
  279. @Override
  280. public void onConfigurationChanged(Configuration config) {
  281. super.onConfigurationChanged(config);
  282. updateTheme(config);
  283. }
  284. protected void updateTheme(Configuration config) {
  285. boolean dark = (config.uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
  286. setDarkMode(dark);
  287. }
  288. }