520 lines
18 KiB
Java
Executable File
520 lines
18 KiB
Java
Executable File
/*
|
|
Licensed to the Apache Software Foundation (ASF) under one
|
|
or more contributor license agreements. See the NOTICE file
|
|
distributed with this work for additional information
|
|
regarding copyright ownership. The ASF licenses this file
|
|
to you under the Apache License, Version 2.0 (the
|
|
"License"); you may not use this file except in compliance
|
|
with the License. You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing,
|
|
software distributed under the License is distributed on an
|
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
KIND, either express or implied. See the License for the
|
|
specific language governing permissions and limitations
|
|
under the License.
|
|
*/
|
|
package org.apache.cordova;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Locale;
|
|
|
|
import org.json.JSONException;
|
|
import org.json.JSONObject;
|
|
|
|
import android.app.Activity;
|
|
import android.app.AlertDialog;
|
|
import android.annotation.SuppressLint;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.content.res.Configuration;
|
|
import android.graphics.Color;
|
|
import android.media.AudioManager;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.view.Menu;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.view.Window;
|
|
import android.view.WindowManager;
|
|
import android.webkit.WebViewClient;
|
|
import android.widget.FrameLayout;
|
|
|
|
/**
|
|
* This class is the main Android activity that represents the Cordova
|
|
* application. It should be extended by the user to load the specific
|
|
* html file that contains the application.
|
|
*
|
|
* As an example:
|
|
*
|
|
* <pre>
|
|
* package org.apache.cordova.examples;
|
|
*
|
|
* import android.os.Bundle;
|
|
* import org.apache.cordova.*;
|
|
*
|
|
* public class Example extends CordovaActivity {
|
|
* @Override
|
|
* public void onCreate(Bundle savedInstanceState) {
|
|
* super.onCreate(savedInstanceState);
|
|
* super.init();
|
|
* // Load your application
|
|
* loadUrl(launchUrl);
|
|
* }
|
|
* }
|
|
* </pre>
|
|
*
|
|
* Cordova xml configuration: Cordova uses a configuration file at
|
|
* res/xml/config.xml to specify its settings. See "The config.xml File"
|
|
* guide in cordova-docs at http://cordova.apache.org/docs for the documentation
|
|
* for the configuration. The use of the set*Property() methods is
|
|
* deprecated in favor of the config.xml file.
|
|
*
|
|
*/
|
|
public class CordovaActivity extends Activity {
|
|
public static String TAG = "CordovaActivity";
|
|
|
|
// The webview for our app
|
|
protected CordovaWebView appView;
|
|
|
|
private static int ACTIVITY_STARTING = 0;
|
|
private static int ACTIVITY_RUNNING = 1;
|
|
private static int ACTIVITY_EXITING = 2;
|
|
|
|
// Keep app running when pause is received. (default = true)
|
|
// If true, then the JavaScript and native code continue to run in the background
|
|
// when another application (activity) is started.
|
|
protected boolean keepRunning = true;
|
|
|
|
// Flag to keep immersive mode if set to fullscreen
|
|
protected boolean immersiveMode;
|
|
|
|
// Read from config.xml:
|
|
protected CordovaPreferences preferences;
|
|
protected String launchUrl;
|
|
protected ArrayList<PluginEntry> pluginEntries;
|
|
protected CordovaInterfaceImpl cordovaInterface;
|
|
|
|
/**
|
|
* Called when the activity is first created.
|
|
*/
|
|
@Override
|
|
public void onCreate(Bundle savedInstanceState) {
|
|
// need to activate preferences before super.onCreate to avoid "requestFeature() must be called before adding content" exception
|
|
loadConfig();
|
|
|
|
String logLevel = preferences.getString("loglevel", "ERROR");
|
|
LOG.setLogLevel(logLevel);
|
|
|
|
LOG.i(TAG, "Apache Cordova native platform version " + CordovaWebView.CORDOVA_VERSION + " is starting");
|
|
LOG.d(TAG, "CordovaActivity.onCreate()");
|
|
|
|
if (!preferences.getBoolean("ShowTitle", false)) {
|
|
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
|
|
}
|
|
|
|
if (preferences.getBoolean("SetFullscreen", false)) {
|
|
LOG.d(TAG, "The SetFullscreen configuration is deprecated in favor of Fullscreen, and will be removed in a future version.");
|
|
preferences.set("Fullscreen", true);
|
|
}
|
|
if (preferences.getBoolean("Fullscreen", false)) {
|
|
// NOTE: use the FullscreenNotImmersive configuration key to set the activity in a REAL full screen
|
|
// (as was the case in previous cordova versions)
|
|
if (!preferences.getBoolean("FullscreenNotImmersive", false)) {
|
|
immersiveMode = true;
|
|
} else {
|
|
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
}
|
|
} else {
|
|
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN,
|
|
WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
|
|
}
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
cordovaInterface = makeCordovaInterface();
|
|
if (savedInstanceState != null) {
|
|
cordovaInterface.restoreInstanceState(savedInstanceState);
|
|
}
|
|
}
|
|
|
|
protected void init() {
|
|
appView = makeWebView();
|
|
createViews();
|
|
if (!appView.isInitialized()) {
|
|
appView.init(cordovaInterface, pluginEntries, preferences);
|
|
}
|
|
cordovaInterface.onCordovaInit(appView.getPluginManager());
|
|
|
|
// Wire the hardware volume controls to control media if desired.
|
|
String volumePref = preferences.getString("DefaultVolumeStream", "");
|
|
if ("media".equals(volumePref.toLowerCase(Locale.ENGLISH))) {
|
|
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("deprecation")
|
|
protected void loadConfig() {
|
|
ConfigXmlParser parser = new ConfigXmlParser();
|
|
parser.parse(this);
|
|
preferences = parser.getPreferences();
|
|
preferences.setPreferencesBundle(getIntent().getExtras());
|
|
launchUrl = parser.getLaunchUrl();
|
|
pluginEntries = parser.getPluginEntries();
|
|
Config.parser = parser;
|
|
}
|
|
|
|
//Suppressing warnings in AndroidStudio
|
|
@SuppressWarnings({"deprecation", "ResourceType"})
|
|
protected void createViews() {
|
|
//Why are we setting a constant as the ID? This should be investigated
|
|
appView.getView().setId(100);
|
|
appView.getView().setLayoutParams(new FrameLayout.LayoutParams(
|
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
ViewGroup.LayoutParams.MATCH_PARENT));
|
|
|
|
setContentView(appView.getView());
|
|
|
|
if (preferences.contains("BackgroundColor")) {
|
|
try {
|
|
int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
|
|
// Background of activity:
|
|
appView.getView().setBackgroundColor(backgroundColor);
|
|
}
|
|
catch (NumberFormatException e){
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
appView.getView().requestFocusFromTouch();
|
|
}
|
|
|
|
/**
|
|
* Construct the default web view object.
|
|
* <p/>
|
|
* Override this to customize the webview that is used.
|
|
*/
|
|
protected CordovaWebView makeWebView() {
|
|
return new CordovaWebViewImpl(makeWebViewEngine());
|
|
}
|
|
|
|
protected CordovaWebViewEngine makeWebViewEngine() {
|
|
return CordovaWebViewImpl.createEngine(this, preferences);
|
|
}
|
|
|
|
protected CordovaInterfaceImpl makeCordovaInterface() {
|
|
return new CordovaInterfaceImpl(this) {
|
|
@Override
|
|
public Object onMessage(String id, Object data) {
|
|
// Plumb this to CordovaActivity.onMessage for backwards compatibility
|
|
return CordovaActivity.this.onMessage(id, data);
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Load the url into the webview.
|
|
*/
|
|
public void loadUrl(String url) {
|
|
if (appView == null) {
|
|
init();
|
|
}
|
|
|
|
// If keepRunning
|
|
this.keepRunning = preferences.getBoolean("KeepRunning", true);
|
|
|
|
appView.loadUrlIntoView(url, true);
|
|
}
|
|
|
|
/**
|
|
* Called when the system is about to start resuming a previous activity.
|
|
*/
|
|
@Override
|
|
protected void onPause() {
|
|
super.onPause();
|
|
LOG.d(TAG, "Paused the activity.");
|
|
|
|
if (this.appView != null) {
|
|
// CB-9382 If there is an activity that started for result and main activity is waiting for callback
|
|
// result, we shoudn't stop WebView Javascript timers, as activity for result might be using them
|
|
boolean keepRunning = this.keepRunning || this.cordovaInterface.activityResultCallback != null;
|
|
this.appView.handlePause(keepRunning);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when the activity receives a new intent
|
|
*/
|
|
@Override
|
|
protected void onNewIntent(Intent intent) {
|
|
super.onNewIntent(intent);
|
|
//Forward to plugins
|
|
if (this.appView != null)
|
|
this.appView.onNewIntent(intent);
|
|
}
|
|
|
|
/**
|
|
* Called when the activity will start interacting with the user.
|
|
*/
|
|
@Override
|
|
protected void onResume() {
|
|
super.onResume();
|
|
LOG.d(TAG, "Resumed the activity.");
|
|
|
|
if (this.appView == null) {
|
|
return;
|
|
}
|
|
// Force window to have focus, so application always
|
|
// receive user input. Workaround for some devices (Samsung Galaxy Note 3 at least)
|
|
this.getWindow().getDecorView().requestFocus();
|
|
|
|
this.appView.handleResume(this.keepRunning);
|
|
}
|
|
|
|
/**
|
|
* Called when the activity is no longer visible to the user.
|
|
*/
|
|
@Override
|
|
protected void onStop() {
|
|
super.onStop();
|
|
LOG.d(TAG, "Stopped the activity.");
|
|
|
|
if (this.appView == null) {
|
|
return;
|
|
}
|
|
this.appView.handleStop();
|
|
}
|
|
|
|
/**
|
|
* Called when the activity is becoming visible to the user.
|
|
*/
|
|
@Override
|
|
protected void onStart() {
|
|
super.onStart();
|
|
LOG.d(TAG, "Started the activity.");
|
|
|
|
if (this.appView == null) {
|
|
return;
|
|
}
|
|
this.appView.handleStart();
|
|
}
|
|
|
|
/**
|
|
* The final call you receive before your activity is destroyed.
|
|
*/
|
|
@Override
|
|
public void onDestroy() {
|
|
LOG.d(TAG, "CordovaActivity.onDestroy()");
|
|
super.onDestroy();
|
|
|
|
if (this.appView != null) {
|
|
appView.handleDestroy();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when view focus is changed
|
|
*/
|
|
@SuppressLint("InlinedApi")
|
|
@Override
|
|
public void onWindowFocusChanged(boolean hasFocus) {
|
|
super.onWindowFocusChanged(hasFocus);
|
|
if (hasFocus && immersiveMode) {
|
|
final int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
|
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
|
|
|
getWindow().getDecorView().setSystemUiVisibility(uiOptions);
|
|
}
|
|
}
|
|
|
|
@SuppressLint("NewApi")
|
|
@Override
|
|
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
|
|
// Capture requestCode here so that it is captured in the setActivityResultCallback() case.
|
|
cordovaInterface.setActivityResultRequestCode(requestCode);
|
|
super.startActivityForResult(intent, requestCode, options);
|
|
}
|
|
|
|
/**
|
|
* Called when an activity you launched exits, giving you the requestCode you started it with,
|
|
* the resultCode it returned, and any additional data from it.
|
|
*
|
|
* @param requestCode The request code originally supplied to startActivityForResult(),
|
|
* allowing you to identify who this result came from.
|
|
* @param resultCode The integer result code returned by the child activity through its setResult().
|
|
* @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
|
|
*/
|
|
@Override
|
|
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
|
LOG.d(TAG, "Incoming Result. Request code = " + requestCode);
|
|
super.onActivityResult(requestCode, resultCode, intent);
|
|
cordovaInterface.onActivityResult(requestCode, resultCode, intent);
|
|
}
|
|
|
|
/**
|
|
* Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
|
|
* The errorCode parameter corresponds to one of the ERROR_* constants.
|
|
*
|
|
* @param errorCode The error code corresponding to an ERROR_* value.
|
|
* @param description A String describing the error.
|
|
* @param failingUrl The url that failed to load.
|
|
*/
|
|
public void onReceivedError(final int errorCode, final String description, final String failingUrl) {
|
|
final CordovaActivity me = this;
|
|
|
|
// If errorUrl specified, then load it
|
|
final String errorUrl = preferences.getString("errorUrl", null);
|
|
if ((errorUrl != null) && (!failingUrl.equals(errorUrl)) && (appView != null)) {
|
|
// Load URL on UI thread
|
|
me.runOnUiThread(new Runnable() {
|
|
public void run() {
|
|
me.appView.showWebPage(errorUrl, false, true, null);
|
|
}
|
|
});
|
|
}
|
|
// If not, then display error dialog
|
|
else {
|
|
final boolean exit = !(errorCode == WebViewClient.ERROR_HOST_LOOKUP);
|
|
me.runOnUiThread(new Runnable() {
|
|
public void run() {
|
|
if (exit) {
|
|
me.appView.getView().setVisibility(View.GONE);
|
|
me.displayError("Application Error", description + " (" + failingUrl + ")", "OK", exit);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display an error dialog and optionally exit application.
|
|
*/
|
|
public void displayError(final String title, final String message, final String button, final boolean exit) {
|
|
final CordovaActivity me = this;
|
|
me.runOnUiThread(new Runnable() {
|
|
public void run() {
|
|
try {
|
|
AlertDialog.Builder dlg = new AlertDialog.Builder(me);
|
|
dlg.setMessage(message);
|
|
dlg.setTitle(title);
|
|
dlg.setCancelable(false);
|
|
dlg.setPositiveButton(button,
|
|
new AlertDialog.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
dialog.dismiss();
|
|
if (exit) {
|
|
finish();
|
|
}
|
|
}
|
|
});
|
|
dlg.create();
|
|
dlg.show();
|
|
} catch (Exception e) {
|
|
finish();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/*
|
|
* Hook in Cordova for menu plugins
|
|
*/
|
|
@Override
|
|
public boolean onCreateOptionsMenu(Menu menu) {
|
|
if (appView != null) {
|
|
appView.getPluginManager().postMessage("onCreateOptionsMenu", menu);
|
|
}
|
|
return super.onCreateOptionsMenu(menu);
|
|
}
|
|
|
|
@Override
|
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
|
if (appView != null) {
|
|
appView.getPluginManager().postMessage("onPrepareOptionsMenu", menu);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
if (appView != null) {
|
|
appView.getPluginManager().postMessage("onOptionsItemSelected", item);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Called when a message is sent to plugin.
|
|
*
|
|
* @param id The message id
|
|
* @param data The message data
|
|
* @return Object or null
|
|
*/
|
|
public Object onMessage(String id, Object data) {
|
|
if ("onReceivedError".equals(id)) {
|
|
JSONObject d = (JSONObject) data;
|
|
try {
|
|
this.onReceivedError(d.getInt("errorCode"), d.getString("description"), d.getString("url"));
|
|
} catch (JSONException e) {
|
|
e.printStackTrace();
|
|
}
|
|
} else if ("exit".equals(id)) {
|
|
finish();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
protected void onSaveInstanceState(Bundle outState) {
|
|
cordovaInterface.onSaveInstanceState(outState);
|
|
super.onSaveInstanceState(outState);
|
|
}
|
|
|
|
/**
|
|
* Called by the system when the device configuration changes while your activity is running.
|
|
*
|
|
* @param newConfig The new device configuration
|
|
*/
|
|
@Override
|
|
public void onConfigurationChanged(Configuration newConfig) {
|
|
super.onConfigurationChanged(newConfig);
|
|
if (this.appView == null) {
|
|
return;
|
|
}
|
|
PluginManager pm = this.appView.getPluginManager();
|
|
if (pm != null) {
|
|
pm.onConfigurationChanged(newConfig);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by the system when the user grants permissions
|
|
*
|
|
* @param requestCode
|
|
* @param permissions
|
|
* @param grantResults
|
|
*/
|
|
@Override
|
|
public void onRequestPermissionsResult(int requestCode, String permissions[],
|
|
int[] grantResults) {
|
|
try
|
|
{
|
|
cordovaInterface.onRequestPermissionResult(requestCode, permissions, grantResults);
|
|
}
|
|
catch (JSONException e)
|
|
{
|
|
LOG.d(TAG, "JSONException: Parameters fed into the method are not valid");
|
|
e.printStackTrace();
|
|
}
|
|
|
|
}
|
|
|
|
}
|