Java tutorial
/* Copyright (c) Sybase, Inc. 2012 All rights reserved. In addition to the license terms set out in the Sybase License Agreement for the Sybase Unwired Platform ("Program"), the following additional or different rights and accompanying obligations and restrictions shall apply to the source code in this file ("Code"). Sybase grants you a limited, non-exclusive, non-transferable, revocable license to use, reproduce, and modify the Code solely for purposes of (i) maintaining the Code as reference material to better understand the operation of the Program, and (ii) development and testing of applications created in connection with your licensed use of the Program. The Code may not be transferred, sold, assigned, sublicensed or otherwise conveyed (whether by operation of law or otherwise) to another party without Sybase's prior written consent. The following provisions shall apply to any modifications you make to the Code: (i) Sybase will not provide any maintenance or support for modified Code or problems that result from use of modified Code; (ii) Sybase expressly disclaims any warranties and conditions, express or implied, relating to modified Code or any problems that result from use of the modified Code; (iii) SYBASE SHALL NOT BE LIABLE FOR ANY LOSS OR DAMAGE RELATING TO MODIFICATIONS MADE TO THE CODE OR FOR ANY DAMAGES RESULTING FROM USE OF THE MODIFIED CODE, INCLUDING, WITHOUT LIMITATION, ANY INACCURACY OF DATA, LOSS OF PROFITS OR DIRECT, INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES, EVEN IF SYBASE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES; (iv) you agree to indemnify, hold harmless, and defend Sybase from and against any claims or lawsuits, including attorney's fees, that arise from or are related to the modified Code or from use of the modified Code. */ package com.aslanoba.hwc; import org.apache.cordova.CordovaChromeClient; import org.apache.cordova.CordovaWebView; import org.apache.cordova.CordovaWebViewClient; import org.apache.cordova.DroidGap; import org.apache.cordova.api.CordovaInterface; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.Display; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.webkit.ConsoleMessage; import android.webkit.ConsoleMessage.MessageLevel; import android.webkit.GeolocationPermissions; import android.webkit.JsResult; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import com.google.android.gcm.GCMRegistrar; import com.sybase.hybridApp.AndroidHybridAppHelper; import com.sybase.hybridApp.HybridAppDb; import com.sybase.hybridApp.UiHybridAppController; import com.sybase.hybridApp.amp.Consts; import com.sybase.mo.MocaLog; import com.sybase.mo.MocaLog.eMocaLogLevel; /** * HybridAppContainer is the activity that displays the WebView hosting the * hybrid app. */ public class UiHybridAppContainer extends DroidGap { private static final String LOG_TAG = "HybridAppContainer"; // Receiver for challenges private ChallengeListener m_oChallengeListener = new ChallengeListener(this); private UiHybridAppController m_oHybridAppController = null; private WebView m_oWebView; private String m_sProgressText = ""; private ViewTreeObserver.OnGlobalLayoutListener m_oLayoutListener; private boolean m_bOrientationChanged = false; private boolean m_bInitialized = false; // Helper to turn PhoneGap integration on/off private static final boolean USE_PHONEGAP = true; private boolean m_errorLoading = false; @Override public boolean onPrepareOptionsMenu(Menu oMenu) { m_oHybridAppController.onPrepareOptionsMenu(oMenu); //SMPONP-12445 Android: Menbar almost covered the whole screen after calling hwc.removeAllMenuItems() //If this is no Menu item added, the menu won't be shown. if (oMenu != null && oMenu.size() <= 0) { return false; } return super.onPrepareOptionsMenu(oMenu); } @Override public boolean onOptionsItemSelected(MenuItem oItem) { m_oHybridAppController.onOptionsItemSelected(oItem); return super.onOptionsItemSelected(oItem); } @Override public void onCreate(Bundle savedInstanceState) { super.setBooleanProperty("showTitle", true); super.onCreate(savedInstanceState); GCMRegistrar.checkManifest(this); final String regid = GCMRegistrar.getRegistrationId(this); Log.i("GCM Started - onCreate", "Check registration :" + regid); String SENDER_ID = "741064895497"; if (regid.equals("")) { GCMRegistrar.register(this, SENDER_ID); } } @Override public void onResume() { super.onResume(); GCMRegistrar.checkManifest(this); final String regid = GCMRegistrar.getRegistrationId(this); Log.i("GCM Started - onResume", "Check registration :" + regid); String SENDER_ID = "741064895497"; if (regid.equals("")) { GCMRegistrar.register(this, SENDER_ID); } // check to see if we need to redirect to the password entry or // change screen if (PasswordPolicyMgr.redirect(this)) return; if (!m_bInitialized) { try { initWebView(); } finally { m_bInitialized = true; } } m_oHybridAppController.onResume(); // Register challenge listener HybridWebApplication.getInstance().setChallengeListener(m_oChallengeListener); } private void initWebView() { Intent oIntent = getIntent(); m_errorLoading = false; // PhoneGap Change: Layout provided by PhoneGap if (USE_PHONEGAP) { // PhoneGap Change: We must call DroidGap.init right away // so that the WebView is created CordovaWebView webView = new CordovaWebView(this); CordovaWebViewClient webViewClient = new HybridAppCordovaWebViewClient(this); CordovaChromeClient chromeClient = new HybridAppCordovaChromeClient(this, webView); super.init(webView, webViewClient, chromeClient); // <----- must // comment this out // if we no longer // inherit from // PhoneGap m_oWebView = this.appView; } else { setContentView(R.layout.hybridappcontainer); m_oWebView = (WebView) findViewById(R.id.webview); } int iStartMode = oIntent.getIntExtra(Consts.INTENT_PARAM_HYBRIDAPP_START_MODE, Consts.START_MODE_INVALID); if (iStartMode == Consts.START_MODE_MESSAGE) { int iMessageID = oIntent.getIntExtra(Consts.INTENT_PARAM_HYBRIDAPP_MSG_ID, 0); int iModuleID = oIntent.getIntExtra(Consts.INTENT_PARAM_HYBRIDAPP_MODULE_ID, 0); int iModuleVersion = oIntent.getIntExtra(Consts.INTENT_PARAM_HYBRIDAPP_MODULE_VERSION, -1); m_sProgressText = oIntent.getStringExtra(Consts.INTENT_PARAM_HYBRIDAPP_PROGRESS_TEXT); m_oHybridAppController = new UiHybridAppController(this, m_oWebView, iMessageID, iModuleID, iModuleVersion); } else if (iStartMode == Consts.START_MODE_HYBRIDAPP) { int iHybridAppID = oIntent.getIntExtra(Consts.INTENT_PARAM_HYBRIDAPP_ID, 0); m_sProgressText = oIntent.getStringExtra(Consts.INTENT_PARAM_HYBRIDAPP_PROGRESS_TEXT); m_oHybridAppController = new UiHybridAppController(this, m_oWebView, iHybridAppID); } else { setResult(Activity.RESULT_OK, null); finish(); return; } // Check that controller was initialized. This could fail if the HybridApp // or message is missing if (!m_oHybridAppController.isInitialized()) { setResult(Activity.RESULT_OK, null); finish(); return; } // Register a layout changed listener to capture screen orientation // event(after orientation happens) View root = findViewById(android.R.id.content); ViewTreeObserver obs = root.getViewTreeObserver(); m_oLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (m_bOrientationChanged) { // call into javascript to notify an orientation change m_oWebView.loadUrl("javascript:onFrameResize();"); m_bOrientationChanged = false; } } }; obs.addOnGlobalLayoutListener(m_oLayoutListener); // Already set in the xml but due to Android bug in 2.2, must be // re-applied here. m_oWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); WebSettings webSettings = m_oWebView.getSettings(); webSettings.setSavePassword(false); webSettings.setSaveFormData(false); webSettings.setJavaScriptEnabled(true); // Setup localStorage webSettings.setDomStorageEnabled(true); AndroidHybridAppHelper.getInstance().setWebSettingsDatabasePath(webSettings, m_oHybridAppController); // Setup geolocation webSettings.setGeolocationEnabled(true); AndroidHybridAppHelper.getInstance().setWebSettingsGeolocationDatabasePath(webSettings, m_oHybridAppController); // PhoneGap Change: We use the PhoneGap version of these clients if (!USE_PHONEGAP) { m_oWebView.setWebChromeClient(new HybridAppWebChromeClient(m_oWebView)); m_oWebView.setWebViewClient(new HybridAppWebViewClient()); } String sBaseURL = m_oHybridAppController.getLaunchURL(); // We can't directly load the url as some devices(Xoom) don't like having // a query string on the content provider url // Instead we provide the html data and url and let the content provider // handle any resources(js,css) that are linked in byte[] abData = m_oHybridAppController.getLaunchURLData(); // PhoneGap Change: We must call through PhoneGap to load the URL if (USE_PHONEGAP) { // PhoneGap takes cares of the loading spinner super.setStringProperty("loadingDialog", m_sProgressText); // PhoneGap may timeout loading the web page super.setIntegerProperty("loadUrlTimeoutValue", 300000); // PhoneGap will load the URL super.loadUrlWithData(sBaseURL, abData); } else { m_oWebView.loadDataWithBaseURL(sBaseURL, new String(abData), null, "utf-8", null); } } @Override protected void onPause() { // Unregister challenge listener HybridWebApplication.getInstance().setChallengeListener(null); if (m_oHybridAppController != null) m_oHybridAppController.onPause(); super.onPause(); } @Override public void onDestroy() { super.onDestroy(); View root = findViewById(android.R.id.content); ViewTreeObserver obs = root.getViewTreeObserver(); obs.removeGlobalOnLayoutListener(m_oLayoutListener); if (m_oWebView != null) { // Need to explicitly wipe the state of the webview // so the scriptable objects are GC m_oWebView.destroy(); m_oWebView = null; } if (m_oHybridAppController != null) m_oHybridAppController.onDestroy(); m_oHybridAppController = null; } @Override public void onUserInteraction() { // check to see if we need to redirect to the password entry or // change screen PasswordPolicyMgr.redirect(this); PasswordPolicyMgr.resetIdleCounter(); } @Override public void onActivityResult(final int iRequest, final int iResult, final Intent oData) { // Process platform onActivityResult in another thread to avoid dead lock. Thread tRunner = new Thread(new Runnable() { @Override public void run() { processActivityResult(iRequest, iResult, oData); } }); tRunner.start(); } /** * Process platform onActivityResult * * @param iRequest * @param iResult * @param oData */ private void processActivityResult(int iRequest, int iResult, Intent oData) { super.onActivityResult(iRequest, iResult, oData); m_oHybridAppController.onActivityResult(iRequest, iResult, oData); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (m_oHybridAppController.onKeyDown(keyCode, event)) { return true; } // Default behavior return super.onKeyDown(keyCode, event); } class HybridAppWebChromeClient extends WebChromeClient { private WebView m_webView; HybridAppWebChromeClient(WebView webView) { m_webView = webView; } public boolean onJsAlert(WebView view, String url, String message, final JsResult result) { new AlertDialog.Builder(UiHybridAppContainer.this).setTitle("HybridApp").setMessage(message) .setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() { public void onClick(DialogInterface dialog, int which) { result.confirm(); } }).setCancelable(false).create().show(); return true; } public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { // Allow access to geolocation callback.invoke(origin, true, true); } @Override public boolean onConsoleMessage(ConsoleMessage consoleMessage) { if (handleJSConsoleMessage(consoleMessage, m_webView) == false) { return super.onConsoleMessage(consoleMessage); } return true; } } /** * HybridAppWebViewClient extends the WebViewClient The purpose of this class * is to provide any possible actions when the web page is loading. Currently * this displays the progress dialog when a page is being loaded * */ class HybridAppWebViewClient extends WebViewClient { private ProgressDialog m_oProgressDlg = null; /** * Shows a spinner on the screen */ private void showProgressDialog(final String sMessage) { runOnUiThread(new Runnable() { @Override public void run() { if (m_oProgressDlg == null) { // Create progress dialog that cannot be canceled m_oProgressDlg = ProgressDialog.show(UiHybridAppContainer.this, null, sMessage, false); } } }); } /** * Hides the spinner on the screen */ private void hideProgressDialog() { runOnUiThread(new Runnable() { @Override public void run() { try { if (m_oProgressDlg != null) { // when you dismiss the dialog the spinner stops and will // not spin if you show it again m_oProgressDlg.dismiss(); m_oProgressDlg = null; } } catch (Exception ex) { MocaLog.getAmpHostLog().logMessage("Dismiss progress exception: " + ex.getMessage(), MocaLog.eMocaLogLevel.Normal); } } }); } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Log.d(LOG_TAG, "shouldOverrideUrlLoading" + url); // Fix for 3.x devices. On first touch event we may receive an about:blank navigation. if (url.equals("about:blank")) { return true; } // Local content we load within the browser. This may be an iframe. else if (url.indexOf(m_oHybridAppController.getProviderURL().toString()) == 0) { return false; } else { // We don't want to load any external links in our browser // Open these with an external viewer (mailto, tel, ...) Intent oIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); startActivity(oIntent); return true; } } @Override public void onLoadResource(WebView view, String url) { Log.d(LOG_TAG, "onLoadResource" + url); MocaLog.getAmpHostLog().logMessage( Consts.LOG_PREFIX_HYBRIDAPP_CONTAINER + "WebViewClient onloadresource: " + url, MocaLog.eMocaLogLevel.Normal); } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { showProgressDialog(m_sProgressText); Log.d(LOG_TAG, "onPageStarted" + url); MocaLog.getAmpHostLog().logMessage( Consts.LOG_PREFIX_HYBRIDAPP_CONTAINER + "WebViewClient onpagestarted: " + url, MocaLog.eMocaLogLevel.Normal); } @Override public void onPageFinished(WebView view, String url) { hideProgressDialog(); Log.d(LOG_TAG, "onPageFinished" + url); MocaLog.getAmpHostLog().logMessage( Consts.LOG_PREFIX_HYBRIDAPP_CONTAINER + "WebViewClient onpageFinished: " + url, MocaLog.eMocaLogLevel.Normal); } @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { Log.d(LOG_TAG, "onReceivedError" + failingUrl); MocaLog.getAmpHostLog().logMessage( Consts.LOG_PREFIX_HYBRIDAPP_CONTAINER + "WebViewClient onReceiveError: " + failingUrl + ", description: " + description + ", errorCode: " + errorCode, MocaLog.eMocaLogLevel.Normal); } } /** * PhoneGap web view client to indicate when the hybrid app javascript is * loaded for receiving javascript call */ class HybridAppCordovaWebViewClient extends CordovaWebViewClient { public HybridAppCordovaWebViewClient(DroidGap ctx) { super(ctx); } @Override public void onPageStarted(WebView view, String url, Bitmap bitmap) { try { super.onPageStarted(view, url, bitmap); } catch (Exception ex) { MocaLog.getAmpHostLog().logMessage( Consts.LOG_PREFIX_HYBRIDAPP_CONTAINER + "WebViewClient onPageStarted: " + ex.getMessage(), MocaLog.eMocaLogLevel.Normal); } } @Override public void onPageFinished(WebView view, String url) { if (!m_errorLoading) { super.onPageFinished(view, url); m_oHybridAppController.setIsHybridAppLoaded(true); } } @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { super.onReceivedError(view, errorCode, description, failingUrl); } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Log.d(LOG_TAG, "shouldOverrideUrlLoading" + url); // Fix for 3.x devices. On first touch event we may receive an about:blank navigation. if (url.equals("about:blank")) { return true; } else { return super.shouldOverrideUrlLoading(view, url); } } } /** * Class that handles callbacks from the Javascript engine. */ class HybridAppCordovaChromeClient extends CordovaChromeClient { private WebView m_webView; public HybridAppCordovaChromeClient(CordovaInterface client, WebView webView) { super(client); m_webView = webView; } /** * Handle any javascript errors during hybridapp loading. */ @Override public boolean onConsoleMessage(ConsoleMessage consoleMessage) { if (handleJSConsoleMessage(consoleMessage, m_webView) == false) { return super.onConsoleMessage(consoleMessage); } return true; } } /** * Handles any javascript errors during HybridApp loading. If the * consoleMessage is an error, this method displays an alert dialog to inform * the user about the error. When the user clicks the OK button, the current * activity is terminated by calling the finish() method and control goes * back to the previous activity. * * @param consoleMessage * @return true if the message is handled by this method. */ private boolean handleJSConsoleMessage(ConsoleMessage consoleMessage, WebView webView) { if (consoleMessage != null && consoleMessage.messageLevel() == MessageLevel.ERROR) { MocaLog.getAmpHostLog().logMessage( "Captured Javascript error. Details: " + consoleMessage.message() + " at " + consoleMessage.lineNumber() + " in " + consoleMessage.sourceId(), eMocaLogLevel.Normal); if (showAlertOnJavaScriptError()) { if (m_errorLoading) { // this is the another error that is being caught. // There is no point displaying another dialog as the user is // seeing one more dialog on the screen. // so, lets just swallow this. return true; } m_errorLoading = true; // signal to the webview to stop loading. webView.stopLoading(); new AlertDialog.Builder(UiHybridAppContainer.this).setTitle(android.R.string.dialog_alert_title) .setMessage("(Javascript)" + consoleMessage.message()) .setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() { public void onClick(DialogInterface dialog, int which) { m_oHybridAppController.setIsHybridAppLoaded(false); // causes the activity to be removed from the history // stack // & control goes back to the earlier activity. // DroidGap specific method that invokes finish(). It // is better to call this method to let PhoneGap // handle cleanup as well. endActivity(); } }).setCancelable(false).create().show(); return true; } } return false; } /** * To check whether show alert when there is JavaScript error. * @return true show alert box when there is error. Otherwise false. */ private boolean showAlertOnJavaScriptError() { // default don't show alert box, just log error message. // there is backward compatibility issues if we show alert message // on every java script error. As android platform is more tolerable on // some JavaScript errors, some applications still works fine under // JavaScript errors. return false; } /** * Let web client get the orientation change notification after the * orientation happens, ViewTreeObserver.OnGlobalLayoutListener is hooked up * in onCreate and will be triggered after orientation event. */ @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); Display display = ((WindowManager) getSystemService(WINDOW_SERVICE)).getDefaultDisplay(); int currOrientation = display.getOrientation(); // check if the orientation is changed, filtering out other type of // changes if (newConfig.orientation != currOrientation) { m_bOrientationChanged = true; } } /** * open client initiated default hybrid app if available * * @param iTimeoutInMillseconds * timeout to wait for opening the hybrid app * @return true if hybrid app is opened, otherwise false */ public static boolean openClientInitiatedDefaultHybridAppIfAvailable(int iTimeoutInMillseconds) throws Exception { boolean bReturn = false; // Auto start for default app HybridAppDb oHybridApp = (HybridAppDb) HybridAppDb.getDefaultHybridApp(); if (oHybridApp != null && oHybridApp.isUserViewable()) { Intent oIntentHybridAppContainer = new Intent(HybridWebApplication.getInstance(), UiHybridAppContainer.class); oIntentHybridAppContainer.putExtra(Consts.INTENT_PARAM_HYBRIDAPP_START_MODE, Consts.START_MODE_HYBRIDAPP); oIntentHybridAppContainer.putExtra(Consts.INTENT_PARAM_HYBRIDAPP_ID, (((HybridAppDb) oHybridApp).getHybridAppId())); oIntentHybridAppContainer.putExtra(Consts.INTENT_PARAM_HYBRIDAPP_PROGRESS_TEXT, oHybridApp.getDisplayName()); oIntentHybridAppContainer.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); HybridWebApplication.getInstance().startActivity(oIntentHybridAppContainer); // wait the hybrid app is loaded so that it can accept javascript push // notification requests while (true) { // wait until the hybrid app is fully loaded to handle the push // notification by javascript listener if (AndroidHybridAppHelper.getInstance().getHybridAppController() != null) { if (AndroidHybridAppHelper.getInstance().getHybridAppController().getIsHybridAppLoaded()) { bReturn = true; break; } } if (iTimeoutInMillseconds < 0) { throw new Exception("Timeout when starting hybrid app by push notification"); } // sleep 200ms Thread.sleep(200); iTimeoutInMillseconds -= 200; } } return bReturn; } }