CordovaActivity.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  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 java.util.ArrayList;
  19. import java.util.Locale;
  20. import org.json.JSONException;
  21. import org.json.JSONObject;
  22. import android.app.Activity;
  23. import android.app.AlertDialog;
  24. import android.annotation.SuppressLint;
  25. import android.content.DialogInterface;
  26. import android.content.Intent;
  27. import android.content.res.Configuration;
  28. import android.graphics.Color;
  29. import android.media.AudioManager;
  30. import android.os.Build;
  31. import android.os.Bundle;
  32. import android.view.Menu;
  33. import android.view.MenuItem;
  34. import android.view.View;
  35. import android.view.ViewGroup;
  36. import android.view.Window;
  37. import android.view.WindowManager;
  38. import android.webkit.WebViewClient;
  39. import android.widget.FrameLayout;
  40. /**
  41. * This class is the main Android activity that represents the Cordova
  42. * application. It should be extended by the user to load the specific
  43. * html file that contains the application.
  44. *
  45. * As an example:
  46. *
  47. * <pre>
  48. * package org.apache.cordova.examples;
  49. *
  50. * import android.os.Bundle;
  51. * import org.apache.cordova.*;
  52. *
  53. * public class Example extends CordovaActivity {
  54. * &#64;Override
  55. * public void onCreate(Bundle savedInstanceState) {
  56. * super.onCreate(savedInstanceState);
  57. * super.init();
  58. * // Load your application
  59. * loadUrl(launchUrl);
  60. * }
  61. * }
  62. * </pre>
  63. *
  64. * Cordova xml configuration: Cordova uses a configuration file at
  65. * res/xml/config.xml to specify its settings. See "The config.xml File"
  66. * guide in cordova-docs at http://cordova.apache.org/docs for the documentation
  67. * for the configuration. The use of the set*Property() methods is
  68. * deprecated in favor of the config.xml file.
  69. *
  70. */
  71. public class CordovaActivity extends Activity {
  72. public static String TAG = "CordovaActivity";
  73. // The webview for our app
  74. protected CordovaWebView appView;
  75. private static int ACTIVITY_STARTING = 0;
  76. private static int ACTIVITY_RUNNING = 1;
  77. private static int ACTIVITY_EXITING = 2;
  78. // Keep app running when pause is received. (default = true)
  79. // If true, then the JavaScript and native code continue to run in the background
  80. // when another application (activity) is started.
  81. protected boolean keepRunning = true;
  82. // Flag to keep immersive mode if set to fullscreen
  83. protected boolean immersiveMode;
  84. // Read from config.xml:
  85. protected CordovaPreferences preferences;
  86. protected String launchUrl;
  87. protected ArrayList<PluginEntry> pluginEntries;
  88. protected CordovaInterfaceImpl cordovaInterface;
  89. /**
  90. * Called when the activity is first created.
  91. */
  92. @Override
  93. public void onCreate(Bundle savedInstanceState) {
  94. // need to activate preferences before super.onCreate to avoid "requestFeature() must be called before adding content" exception
  95. loadConfig();
  96. String logLevel = preferences.getString("loglevel", "ERROR");
  97. LOG.setLogLevel(logLevel);
  98. LOG.i(TAG, "Apache Cordova native platform version " + CordovaWebView.CORDOVA_VERSION + " is starting");
  99. LOG.d(TAG, "CordovaActivity.onCreate()");
  100. if (!preferences.getBoolean("ShowTitle", false)) {
  101. getWindow().requestFeature(Window.FEATURE_NO_TITLE);
  102. }
  103. if (preferences.getBoolean("SetFullscreen", false)) {
  104. LOG.d(TAG, "The SetFullscreen configuration is deprecated in favor of Fullscreen, and will be removed in a future version.");
  105. preferences.set("Fullscreen", true);
  106. }
  107. if (preferences.getBoolean("Fullscreen", false)) {
  108. // NOTE: use the FullscreenNotImmersive configuration key to set the activity in a REAL full screen
  109. // (as was the case in previous cordova versions)
  110. if (!preferences.getBoolean("FullscreenNotImmersive", false)) {
  111. immersiveMode = true;
  112. } else {
  113. getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
  114. WindowManager.LayoutParams.FLAG_FULLSCREEN);
  115. }
  116. } else {
  117. getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN,
  118. WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
  119. }
  120. super.onCreate(savedInstanceState);
  121. cordovaInterface = makeCordovaInterface();
  122. if (savedInstanceState != null) {
  123. cordovaInterface.restoreInstanceState(savedInstanceState);
  124. }
  125. }
  126. protected void init() {
  127. appView = makeWebView();
  128. createViews();
  129. if (!appView.isInitialized()) {
  130. appView.init(cordovaInterface, pluginEntries, preferences);
  131. }
  132. cordovaInterface.onCordovaInit(appView.getPluginManager());
  133. // Wire the hardware volume controls to control media if desired.
  134. String volumePref = preferences.getString("DefaultVolumeStream", "");
  135. if ("media".equals(volumePref.toLowerCase(Locale.ENGLISH))) {
  136. setVolumeControlStream(AudioManager.STREAM_MUSIC);
  137. }
  138. }
  139. @SuppressWarnings("deprecation")
  140. protected void loadConfig() {
  141. ConfigXmlParser parser = new ConfigXmlParser();
  142. parser.parse(this);
  143. preferences = parser.getPreferences();
  144. preferences.setPreferencesBundle(getIntent().getExtras());
  145. launchUrl = parser.getLaunchUrl();
  146. pluginEntries = parser.getPluginEntries();
  147. Config.parser = parser;
  148. }
  149. //Suppressing warnings in AndroidStudio
  150. @SuppressWarnings({"deprecation", "ResourceType"})
  151. protected void createViews() {
  152. //Why are we setting a constant as the ID? This should be investigated
  153. appView.getView().setId(100);
  154. appView.getView().setLayoutParams(new FrameLayout.LayoutParams(
  155. ViewGroup.LayoutParams.MATCH_PARENT,
  156. ViewGroup.LayoutParams.MATCH_PARENT));
  157. setContentView(appView.getView());
  158. if (preferences.contains("BackgroundColor")) {
  159. try {
  160. int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
  161. // Background of activity:
  162. appView.getView().setBackgroundColor(backgroundColor);
  163. }
  164. catch (NumberFormatException e){
  165. e.printStackTrace();
  166. }
  167. }
  168. appView.getView().requestFocusFromTouch();
  169. }
  170. /**
  171. * Construct the default web view object.
  172. * <p/>
  173. * Override this to customize the webview that is used.
  174. */
  175. protected CordovaWebView makeWebView() {
  176. return new CordovaWebViewImpl(makeWebViewEngine());
  177. }
  178. protected CordovaWebViewEngine makeWebViewEngine() {
  179. return CordovaWebViewImpl.createEngine(this, preferences);
  180. }
  181. protected CordovaInterfaceImpl makeCordovaInterface() {
  182. return new CordovaInterfaceImpl(this) {
  183. @Override
  184. public Object onMessage(String id, Object data) {
  185. // Plumb this to CordovaActivity.onMessage for backwards compatibility
  186. return CordovaActivity.this.onMessage(id, data);
  187. }
  188. };
  189. }
  190. /**
  191. * Load the url into the webview.
  192. */
  193. public void loadUrl(String url) {
  194. if (appView == null) {
  195. init();
  196. }
  197. // If keepRunning
  198. this.keepRunning = preferences.getBoolean("KeepRunning", true);
  199. appView.loadUrlIntoView(url, true);
  200. }
  201. /**
  202. * Called when the system is about to start resuming a previous activity.
  203. */
  204. @Override
  205. protected void onPause() {
  206. super.onPause();
  207. LOG.d(TAG, "Paused the activity.");
  208. if (this.appView != null) {
  209. // CB-9382 If there is an activity that started for result and main activity is waiting for callback
  210. // result, we shoudn't stop WebView Javascript timers, as activity for result might be using them
  211. boolean keepRunning = this.keepRunning || this.cordovaInterface.activityResultCallback != null;
  212. this.appView.handlePause(keepRunning);
  213. }
  214. }
  215. /**
  216. * Called when the activity receives a new intent
  217. */
  218. @Override
  219. protected void onNewIntent(Intent intent) {
  220. super.onNewIntent(intent);
  221. //Forward to plugins
  222. if (this.appView != null)
  223. this.appView.onNewIntent(intent);
  224. }
  225. /**
  226. * Called when the activity will start interacting with the user.
  227. */
  228. @Override
  229. protected void onResume() {
  230. super.onResume();
  231. LOG.d(TAG, "Resumed the activity.");
  232. if (this.appView == null) {
  233. return;
  234. }
  235. if (! this.getWindow().getDecorView().hasFocus()) {
  236. // Force window to have focus, so application always
  237. // receive user input. Workaround for some devices (Samsung Galaxy Note 3 at least)
  238. this.getWindow().getDecorView().requestFocus();
  239. }
  240. this.appView.handleResume(this.keepRunning);
  241. }
  242. /**
  243. * Called when the activity is no longer visible to the user.
  244. */
  245. @Override
  246. protected void onStop() {
  247. super.onStop();
  248. LOG.d(TAG, "Stopped the activity.");
  249. if (this.appView == null) {
  250. return;
  251. }
  252. this.appView.handleStop();
  253. }
  254. /**
  255. * Called when the activity is becoming visible to the user.
  256. */
  257. @Override
  258. protected void onStart() {
  259. super.onStart();
  260. LOG.d(TAG, "Started the activity.");
  261. if (this.appView == null) {
  262. return;
  263. }
  264. this.appView.handleStart();
  265. }
  266. /**
  267. * The final call you receive before your activity is destroyed.
  268. */
  269. @Override
  270. public void onDestroy() {
  271. LOG.d(TAG, "CordovaActivity.onDestroy()");
  272. super.onDestroy();
  273. if (this.appView != null) {
  274. appView.handleDestroy();
  275. }
  276. }
  277. /**
  278. * Called when view focus is changed
  279. */
  280. @SuppressLint("InlinedApi")
  281. @Override
  282. public void onWindowFocusChanged(boolean hasFocus) {
  283. super.onWindowFocusChanged(hasFocus);
  284. if (hasFocus && immersiveMode) {
  285. final int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
  286. | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
  287. | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
  288. | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
  289. | View.SYSTEM_UI_FLAG_FULLSCREEN
  290. | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
  291. getWindow().getDecorView().setSystemUiVisibility(uiOptions);
  292. }
  293. }
  294. @SuppressLint("NewApi")
  295. @Override
  296. public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
  297. // Capture requestCode here so that it is captured in the setActivityResultCallback() case.
  298. cordovaInterface.setActivityResultRequestCode(requestCode);
  299. super.startActivityForResult(intent, requestCode, options);
  300. }
  301. /**
  302. * Called when an activity you launched exits, giving you the requestCode you started it with,
  303. * the resultCode it returned, and any additional data from it.
  304. *
  305. * @param requestCode The request code originally supplied to startActivityForResult(),
  306. * allowing you to identify who this result came from.
  307. * @param resultCode The integer result code returned by the child activity through its setResult().
  308. * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
  309. */
  310. @Override
  311. protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
  312. LOG.d(TAG, "Incoming Result. Request code = " + requestCode);
  313. super.onActivityResult(requestCode, resultCode, intent);
  314. cordovaInterface.onActivityResult(requestCode, resultCode, intent);
  315. }
  316. /**
  317. * Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
  318. * The errorCode parameter corresponds to one of the ERROR_* constants.
  319. *
  320. * @param errorCode The error code corresponding to an ERROR_* value.
  321. * @param description A String describing the error.
  322. * @param failingUrl The url that failed to load.
  323. */
  324. public void onReceivedError(final int errorCode, final String description, final String failingUrl) {
  325. final CordovaActivity me = this;
  326. // If errorUrl specified, then load it
  327. final String errorUrl = preferences.getString("errorUrl", null);
  328. if ((errorUrl != null) && (!failingUrl.equals(errorUrl)) && (appView != null)) {
  329. // Load URL on UI thread
  330. me.runOnUiThread(new Runnable() {
  331. public void run() {
  332. me.appView.showWebPage(errorUrl, false, true, null);
  333. }
  334. });
  335. }
  336. // If not, then display error dialog
  337. else {
  338. final boolean exit = !(errorCode == WebViewClient.ERROR_HOST_LOOKUP);
  339. me.runOnUiThread(new Runnable() {
  340. public void run() {
  341. if (exit) {
  342. me.appView.getView().setVisibility(View.GONE);
  343. me.displayError("Application Error", description + " (" + failingUrl + ")", "OK", exit);
  344. }
  345. }
  346. });
  347. }
  348. }
  349. /**
  350. * Display an error dialog and optionally exit application.
  351. */
  352. public void displayError(final String title, final String message, final String button, final boolean exit) {
  353. final CordovaActivity me = this;
  354. me.runOnUiThread(new Runnable() {
  355. public void run() {
  356. try {
  357. AlertDialog.Builder dlg = new AlertDialog.Builder(me);
  358. dlg.setMessage(message);
  359. dlg.setTitle(title);
  360. dlg.setCancelable(false);
  361. dlg.setPositiveButton(button,
  362. new AlertDialog.OnClickListener() {
  363. public void onClick(DialogInterface dialog, int which) {
  364. dialog.dismiss();
  365. if (exit) {
  366. finish();
  367. }
  368. }
  369. });
  370. dlg.create();
  371. dlg.show();
  372. } catch (Exception e) {
  373. finish();
  374. }
  375. }
  376. });
  377. }
  378. /*
  379. * Hook in Cordova for menu plugins
  380. */
  381. @Override
  382. public boolean onCreateOptionsMenu(Menu menu) {
  383. if (appView != null) {
  384. appView.getPluginManager().postMessage("onCreateOptionsMenu", menu);
  385. }
  386. return super.onCreateOptionsMenu(menu);
  387. }
  388. @Override
  389. public boolean onPrepareOptionsMenu(Menu menu) {
  390. if (appView != null) {
  391. appView.getPluginManager().postMessage("onPrepareOptionsMenu", menu);
  392. }
  393. return true;
  394. }
  395. @Override
  396. public boolean onOptionsItemSelected(MenuItem item) {
  397. if (appView != null) {
  398. appView.getPluginManager().postMessage("onOptionsItemSelected", item);
  399. }
  400. return true;
  401. }
  402. /**
  403. * Called when a message is sent to plugin.
  404. *
  405. * @param id The message id
  406. * @param data The message data
  407. * @return Object or null
  408. */
  409. public Object onMessage(String id, Object data) {
  410. if ("onReceivedError".equals(id)) {
  411. JSONObject d = (JSONObject) data;
  412. try {
  413. this.onReceivedError(d.getInt("errorCode"), d.getString("description"), d.getString("url"));
  414. } catch (JSONException e) {
  415. e.printStackTrace();
  416. }
  417. } else if ("exit".equals(id)) {
  418. finish();
  419. }
  420. return null;
  421. }
  422. protected void onSaveInstanceState(Bundle outState) {
  423. cordovaInterface.onSaveInstanceState(outState);
  424. super.onSaveInstanceState(outState);
  425. }
  426. /**
  427. * Called by the system when the device configuration changes while your activity is running.
  428. *
  429. * @param newConfig The new device configuration
  430. */
  431. @Override
  432. public void onConfigurationChanged(Configuration newConfig) {
  433. super.onConfigurationChanged(newConfig);
  434. if (this.appView == null) {
  435. return;
  436. }
  437. PluginManager pm = this.appView.getPluginManager();
  438. if (pm != null) {
  439. pm.onConfigurationChanged(newConfig);
  440. }
  441. }
  442. /**
  443. * Called by the system when the user grants permissions
  444. *
  445. * @param requestCode
  446. * @param permissions
  447. * @param grantResults
  448. */
  449. @Override
  450. public void onRequestPermissionsResult(int requestCode, String permissions[],
  451. int[] grantResults) {
  452. try
  453. {
  454. cordovaInterface.onRequestPermissionResult(requestCode, permissions, grantResults);
  455. }
  456. catch (JSONException e)
  457. {
  458. LOG.d(TAG, "JSONException: Parameters fed into the method are not valid");
  459. e.printStackTrace();
  460. }
  461. }
  462. }