GoNativeActivity.java 13 KB

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