CoreAndroid.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. /*
  2. Licensed to the Apache Software Foundation (ASF) under one
  3. or more contributor license agreements. See the NOTICE file
  4. distributed with this work for additional information
  5. regarding copyright ownership. The ASF licenses this file
  6. to you under the Apache License, Version 2.0 (the
  7. "License"); you may not use this file except in compliance
  8. with the License. You may obtain a copy of the License at
  9. http://www.apache.org/licenses/LICENSE-2.0
  10. Unless required by applicable law or agreed to in writing,
  11. software distributed under the License is distributed on an
  12. "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  13. KIND, either express or implied. See the License for the
  14. specific language governing permissions and limitations
  15. under the License.
  16. */
  17. package org.apache.cordova;
  18. import org.json.JSONArray;
  19. import org.json.JSONException;
  20. import org.json.JSONObject;
  21. import android.content.BroadcastReceiver;
  22. import android.content.Context;
  23. import android.content.Intent;
  24. import android.content.IntentFilter;
  25. import android.telephony.TelephonyManager;
  26. import android.view.KeyEvent;
  27. import java.lang.reflect.Field;
  28. import java.util.HashMap;
  29. /**
  30. * This class exposes methods in Cordova that can be called from JavaScript.
  31. */
  32. public class CoreAndroid extends CordovaPlugin {
  33. public static final String PLUGIN_NAME = "CoreAndroid";
  34. protected static final String TAG = "CordovaApp";
  35. private BroadcastReceiver telephonyReceiver;
  36. private CallbackContext messageChannel;
  37. private PluginResult pendingResume;
  38. private PluginResult pendingPause;
  39. private final Object messageChannelLock = new Object();
  40. /**
  41. * Send an event to be fired on the Javascript side.
  42. *
  43. * @param action The name of the event to be fired
  44. */
  45. public void fireJavascriptEvent(String action) {
  46. sendEventMessage(action);
  47. }
  48. /**
  49. * Sets the context of the Command. This can then be used to do things like
  50. * get file paths associated with the Activity.
  51. */
  52. @Override
  53. public void pluginInitialize() {
  54. this.initTelephonyReceiver();
  55. }
  56. /**
  57. * Executes the request and returns PluginResult.
  58. *
  59. * @param action The action to execute.
  60. * @param args JSONArry of arguments for the plugin.
  61. * @param callbackContext The callback context from which we were invoked.
  62. * @return A PluginResult object with a status and message.
  63. */
  64. public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
  65. PluginResult.Status status = PluginResult.Status.OK;
  66. String result = "";
  67. try {
  68. if (action.equals("clearCache")) {
  69. this.clearCache();
  70. }
  71. else if (action.equals("show")) {
  72. // This gets called from JavaScript onCordovaReady to show the webview.
  73. // I recommend we change the name of the Message as spinner/stop is not
  74. // indicative of what this actually does (shows the webview).
  75. cordova.getActivity().runOnUiThread(new Runnable() {
  76. public void run() {
  77. webView.getPluginManager().postMessage("spinner", "stop");
  78. }
  79. });
  80. }
  81. else if (action.equals("loadUrl")) {
  82. this.loadUrl(args.getString(0), args.optJSONObject(1));
  83. }
  84. else if (action.equals("cancelLoadUrl")) {
  85. //this.cancelLoadUrl();
  86. }
  87. else if (action.equals("clearHistory")) {
  88. this.clearHistory();
  89. }
  90. else if (action.equals("backHistory")) {
  91. this.backHistory();
  92. }
  93. else if (action.equals("overrideButton")) {
  94. this.overrideButton(args.getString(0), args.getBoolean(1));
  95. }
  96. else if (action.equals("overrideBackbutton")) {
  97. this.overrideBackbutton(args.getBoolean(0));
  98. }
  99. else if (action.equals("exitApp")) {
  100. this.exitApp();
  101. }
  102. else if (action.equals("messageChannel")) {
  103. synchronized(messageChannelLock) {
  104. messageChannel = callbackContext;
  105. if (pendingPause != null) {
  106. sendEventMessage(pendingPause);
  107. pendingPause = null;
  108. }
  109. if (pendingResume != null) {
  110. sendEventMessage(pendingResume);
  111. pendingResume = null;
  112. }
  113. }
  114. return true;
  115. }
  116. callbackContext.sendPluginResult(new PluginResult(status, result));
  117. return true;
  118. } catch (JSONException e) {
  119. callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
  120. return false;
  121. }
  122. }
  123. //--------------------------------------------------------------------------
  124. // LOCAL METHODS
  125. //--------------------------------------------------------------------------
  126. /**
  127. * Clear the resource cache.
  128. */
  129. public void clearCache() {
  130. cordova.getActivity().runOnUiThread(new Runnable() {
  131. public void run() {
  132. webView.clearCache();
  133. }
  134. });
  135. }
  136. /**
  137. * Load the url into the webview.
  138. *
  139. * @param url
  140. * @param props Properties that can be passed in to the Cordova activity (i.e. loadingDialog, wait, ...)
  141. * @throws JSONException
  142. */
  143. public void loadUrl(String url, JSONObject props) throws JSONException {
  144. LOG.d("App", "App.loadUrl("+url+","+props+")");
  145. int wait = 0;
  146. boolean openExternal = false;
  147. boolean clearHistory = false;
  148. // If there are properties, then set them on the Activity
  149. HashMap<String, Object> params = new HashMap<String, Object>();
  150. if (props != null) {
  151. JSONArray keys = props.names();
  152. for (int i = 0; i < keys.length(); i++) {
  153. String key = keys.getString(i);
  154. if (key.equals("wait")) {
  155. wait = props.getInt(key);
  156. }
  157. else if (key.equalsIgnoreCase("openexternal")) {
  158. openExternal = props.getBoolean(key);
  159. }
  160. else if (key.equalsIgnoreCase("clearhistory")) {
  161. clearHistory = props.getBoolean(key);
  162. }
  163. else {
  164. Object value = props.get(key);
  165. if (value == null) {
  166. }
  167. else if (value.getClass().equals(String.class)) {
  168. params.put(key, (String)value);
  169. }
  170. else if (value.getClass().equals(Boolean.class)) {
  171. params.put(key, (Boolean)value);
  172. }
  173. else if (value.getClass().equals(Integer.class)) {
  174. params.put(key, (Integer)value);
  175. }
  176. }
  177. }
  178. }
  179. // If wait property, then delay loading
  180. if (wait > 0) {
  181. try {
  182. synchronized(this) {
  183. this.wait(wait);
  184. }
  185. } catch (InterruptedException e) {
  186. e.printStackTrace();
  187. }
  188. }
  189. this.webView.showWebPage(url, openExternal, clearHistory, params);
  190. }
  191. /**
  192. * Clear page history for the app.
  193. */
  194. public void clearHistory() {
  195. cordova.getActivity().runOnUiThread(new Runnable() {
  196. public void run() {
  197. webView.clearHistory();
  198. }
  199. });
  200. }
  201. /**
  202. * Go to previous page displayed.
  203. * This is the same as pressing the backbutton on Android device.
  204. */
  205. public void backHistory() {
  206. cordova.getActivity().runOnUiThread(new Runnable() {
  207. public void run() {
  208. webView.backHistory();
  209. }
  210. });
  211. }
  212. /**
  213. * Override the default behavior of the Android back button.
  214. * If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
  215. *
  216. * @param override T=override, F=cancel override
  217. */
  218. public void overrideBackbutton(boolean override) {
  219. LOG.i("App", "WARNING: Back Button Default Behavior will be overridden. The backbutton event will be fired!");
  220. webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_BACK, override);
  221. }
  222. /**
  223. * Override the default behavior of the Android volume buttons.
  224. * If overridden, when the volume button is pressed, the "volume[up|down]button" JavaScript event will be fired.
  225. *
  226. * @param button volumeup, volumedown
  227. * @param override T=override, F=cancel override
  228. */
  229. public void overrideButton(String button, boolean override) {
  230. LOG.i("App", "WARNING: Volume Button Default Behavior will be overridden. The volume event will be fired!");
  231. if (button.equals("volumeup")) {
  232. webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_UP, override);
  233. }
  234. else if (button.equals("volumedown")) {
  235. webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_DOWN, override);
  236. }
  237. else if (button.equals("menubutton")) {
  238. webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_MENU, override);
  239. }
  240. }
  241. /**
  242. * Return whether the Android back button is overridden by the user.
  243. *
  244. * @return boolean
  245. */
  246. public boolean isBackbuttonOverridden() {
  247. return webView.isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
  248. }
  249. /**
  250. * Exit the Android application.
  251. */
  252. public void exitApp() {
  253. this.webView.getPluginManager().postMessage("exit", null);
  254. }
  255. /**
  256. * Listen for telephony events: RINGING, OFFHOOK and IDLE
  257. * Send these events to all plugins using
  258. * CordovaActivity.onMessage("telephone", "ringing" | "offhook" | "idle")
  259. */
  260. private void initTelephonyReceiver() {
  261. IntentFilter intentFilter = new IntentFilter();
  262. intentFilter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
  263. //final CordovaInterface mycordova = this.cordova;
  264. this.telephonyReceiver = new BroadcastReceiver() {
  265. @Override
  266. public void onReceive(Context context, Intent intent) {
  267. // If state has changed
  268. if ((intent != null) && intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
  269. if (intent.hasExtra(TelephonyManager.EXTRA_STATE)) {
  270. String extraData = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
  271. if (extraData.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
  272. LOG.i(TAG, "Telephone RINGING");
  273. webView.getPluginManager().postMessage("telephone", "ringing");
  274. }
  275. else if (extraData.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {
  276. LOG.i(TAG, "Telephone OFFHOOK");
  277. webView.getPluginManager().postMessage("telephone", "offhook");
  278. }
  279. else if (extraData.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
  280. LOG.i(TAG, "Telephone IDLE");
  281. webView.getPluginManager().postMessage("telephone", "idle");
  282. }
  283. }
  284. }
  285. }
  286. };
  287. // Register the receiver
  288. webView.getContext().registerReceiver(this.telephonyReceiver, intentFilter);
  289. }
  290. private void sendEventMessage(String action) {
  291. JSONObject obj = new JSONObject();
  292. try {
  293. obj.put("action", action);
  294. } catch (JSONException e) {
  295. LOG.e(TAG, "Failed to create event message", e);
  296. }
  297. PluginResult result = new PluginResult(PluginResult.Status.OK, obj);
  298. if (messageChannel == null) {
  299. LOG.i(TAG, "Request to send event before messageChannel initialised: " + action);
  300. if ("pause".equals(action)) {
  301. pendingPause = result;
  302. } else if ("resume".equals(action)) {
  303. // When starting normally onPause then onResume is called
  304. pendingPause = null;
  305. }
  306. } else {
  307. sendEventMessage(result);
  308. }
  309. }
  310. private void sendEventMessage(PluginResult payload) {
  311. payload.setKeepCallback(true);
  312. if (messageChannel != null) {
  313. messageChannel.sendPluginResult(payload);
  314. }
  315. }
  316. /*
  317. * Unregister the receiver
  318. *
  319. */
  320. public void onDestroy()
  321. {
  322. webView.getContext().unregisterReceiver(this.telephonyReceiver);
  323. }
  324. /**
  325. * Used to send the resume event in the case that the Activity is destroyed by the OS
  326. *
  327. * @param resumeEvent PluginResult containing the payload for the resume event to be fired
  328. */
  329. public void sendResumeEvent(PluginResult resumeEvent) {
  330. // This operation must be synchronized because plugin results that trigger resume
  331. // events can be processed asynchronously
  332. synchronized(messageChannelLock) {
  333. if (messageChannel != null) {
  334. sendEventMessage(resumeEvent);
  335. } else {
  336. // Might get called before the page loads, so we need to store it until the
  337. // messageChannel gets created
  338. this.pendingResume = resumeEvent;
  339. }
  340. }
  341. }
  342. /*
  343. * This needs to be implemented if you wish to use the Camera Plugin or other plugins
  344. * that read the Build Configuration.
  345. *
  346. * Thanks to Phil@Medtronic and Graham Borland for finding the answer and posting it to
  347. * StackOverflow. This is annoying as hell!
  348. *
  349. */
  350. public static Object getBuildConfigValue(Context ctx, String key)
  351. {
  352. try
  353. {
  354. Class<?> clazz = Class.forName(ctx.getPackageName() + ".BuildConfig");
  355. Field field = clazz.getField(key);
  356. return field.get(null);
  357. } catch (ClassNotFoundException e) {
  358. LOG.d(TAG, "Unable to get the BuildConfig, is this built with ANT?");
  359. e.printStackTrace();
  360. } catch (NoSuchFieldException e) {
  361. LOG.d(TAG, key + " is not a valid field. Check your build.gradle");
  362. } catch (IllegalAccessException e) {
  363. LOG.d(TAG, "Illegal Access Exception: Let's print a stack trace.");
  364. e.printStackTrace();
  365. }
  366. return null;
  367. }
  368. }