GoNativeActivity.java 12 KB

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