package mobi.monaca.framework; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.Serializable; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import mobi.monaca.framework.bootloader.LocalFileBootloader; import mobi.monaca.framework.nativeui.UIContext; import mobi.monaca.framework.nativeui.UIUtil; import mobi.monaca.framework.nativeui.UpdateStyleQuery; import mobi.monaca.framework.nativeui.component.Component; import mobi.monaca.framework.nativeui.component.PageComponent; import mobi.monaca.framework.nativeui.component.PageOrientation; import mobi.monaca.framework.nativeui.container.Container; import mobi.monaca.framework.nativeui.container.ToolbarContainer; import mobi.monaca.framework.nativeui.exception.NativeUIException; import mobi.monaca.framework.nativeui.menu.MenuRepresentation; import mobi.monaca.framework.psedo.R; import mobi.monaca.framework.transition.BackgroundDrawable; import mobi.monaca.framework.transition.ClosePageIntent; import mobi.monaca.framework.transition.TransitionParams; import mobi.monaca.framework.util.AssetUtils; import mobi.monaca.framework.util.BenchmarkTimer; import mobi.monaca.framework.util.InputStreamLoader; import mobi.monaca.framework.util.MyLog; import mobi.monaca.framework.util.UrlUtil; import mobi.monaca.framework.view.MonacaPageGingerbreadWebViewClient; import mobi.monaca.framework.view.MonacaPageHoneyCombWebViewClient; import mobi.monaca.framework.view.MonacaWebView; import mobi.monaca.utils.TimeStamp; import mobi.monaca.utils.gcm.GCMPushDataset; import mobi.monaca.utils.log.LogItem; import mobi.monaca.utils.log.LogItem.LogLevel; import mobi.monaca.utils.log.LogItem.Source; import org.apache.commons.io.IOUtils; import org.apache.cordova.CordovaWebView; import org.apache.cordova.CordovaWebViewClient; import org.apache.cordova.DroidGap; import org.json.JSONException; import org.json.JSONObject; import android.R.color; import android.annotation.TargetApi; import android.app.Activity; import android.app.Dialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Handler; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.KeyEvent; import android.view.Menu; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ImageView.ScaleType; import android.widget.LinearLayout; import android.widget.RelativeLayout; /** * This class represent a page of Monaca application. */ public class MonacaPageActivity extends DroidGap { public static final String TRANSITION_PARAM_NAME = "monaca.transition"; public static final String URL_PARAM_NAME = "monaca.url"; public static final String TAG = MonacaPageActivity.class.getSimpleName(); private static final int MONACA_TRANSIT_REQUEST = 9000; protected MonacaURI currentMonacaUri; protected Drawable background = null; protected Handler handler = new Handler(); protected int pageIndex = 0; protected Dialog monacaSplashDialog; private boolean isOnDestroyMonacaCalled = false; protected BroadcastReceiver closePageReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { int level = intent.getIntExtra("level", 0); if (pageIndex >= level) { finish(); } } }; protected BroadcastReceiver pushReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { MyLog.d(TAG, "push broadcast received"); if (isIndex()) { GCMPushDataset p = (GCMPushDataset) intent.getExtras().get(GCMPushDataset.KEY); sendPushToWebView(p); } } }; /** If this flag is true, activity is capable of transition. */ protected boolean isCapableForTransition = true; protected UIContext uiContext = null; protected TransitionParams transitionParams; protected JSONObject infoForJavaScript = new JSONObject(); protected String mCurrentHtml; protected GCMPushDataset pushData; protected MonacaApplication mApp; private PageComponent mPageComponent; @Override public void onCreate(Bundle savedInstance) { mApp = (MonacaApplication) getApplication(); registerReceiver(pushReceiver, new IntentFilter(MonacaNotificationActivity.ACTION_RECEIVED_PUSH)); prepare(); if (VERSION.SDK_INT > VERSION_CODES.JELLY_BEAN) { getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); } super.onCreate(savedInstance); // to clear // getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, // WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); in DroidGap // class getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); // currentMonacaUri is set in prepare() // this if statement and postDelayed is for animation if (MonacaApplication.getPages().size() == 1) { init(); loadUri(currentMonacaUri.getOriginalUrl(), false); } else { init(); loadUiFile(getCurrentUriWithoutOptions()); handler.postDelayed(new Runnable() { @Override public void run() { loadUri(currentMonacaUri.getOriginalUrl(), true); } }, 100); } // dirty fix for android4's strange bug if (transitionParams.animationType == TransitionParams.TransitionAnimationType.MODAL) { overridePendingTransition(mobi.monaca.framework.psedo.R.anim.monaca_dialog_open_enter, mobi.monaca.framework.psedo.R.anim.monaca_dialog_open_exit); } else if (transitionParams.animationType == TransitionParams.TransitionAnimationType.SLIDE_LEFT) { overridePendingTransition(mobi.monaca.framework.psedo.R.anim.monaca_slide_open_enter, mobi.monaca.framework.psedo.R.anim.monaca_slide_open_exit); } else if (transitionParams.animationType == TransitionParams.TransitionAnimationType.SLIDE_RIGHT) { overridePendingTransition(mobi.monaca.framework.psedo.R.anim.monaca_slide_right_open_enter, mobi.monaca.framework.psedo.R.anim.monaca_slide_right_open_exit); } else if (transitionParams.animationType == TransitionParams.TransitionAnimationType.NONE) { overridePendingTransition(mobi.monaca.framework.psedo.R.anim.monaca_none, mobi.monaca.framework.psedo.R.anim.monaca_none); } } protected boolean isIndex() { return pageIndex == MonacaApplication.getPages().size() - 1; } protected Drawable getSplashDrawable() throws IOException { InputStream is = getResources().getAssets().open(MonacaSplashActivity.SPLASH_IMAGE_PATH); return Drawable.createFromStream(is, "splash_default"); } public void showMonacaSplash() { final MonacaPageActivity activity = this; Runnable runnable = new Runnable() { public void run() { // Get reference to display Display display = activity.getWindowManager().getDefaultDisplay(); // Create the layout for the dialog FrameLayout root = new FrameLayout(activity.getActivity()); root.setMinimumHeight(display.getHeight()); root.setMinimumWidth(display.getWidth()); root.setBackgroundColor(mApp.getAppJsonSetting().getSplashBackgroundColor()); root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0.0F)); try { ImageView splashImageView; splashImageView = new ImageView(MonacaPageActivity.this); splashImageView.setImageDrawable(activity.getSplashDrawable()); splashImageView.setScaleType(ScaleType.FIT_CENTER); root.addView(splashImageView); } catch (IOException e) { MyLog.e(TAG, e.getMessage()); } // Create and show the dialog monacaSplashDialog = new Dialog(MonacaPageActivity.this, android.R.style.Theme_Translucent_NoTitleBar); // check to see if the splash screen should be full screen if ((getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) == WindowManager.LayoutParams.FLAG_FULLSCREEN) { monacaSplashDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); } monacaSplashDialog.setContentView(root); monacaSplashDialog.setCancelable(false); monacaSplashDialog.show(); } }; this.runOnUiThread(runnable); } public void removeMonacaSplash() { if (monacaSplashDialog != null && monacaSplashDialog.isShowing()) { monacaSplashDialog.dismiss(); monacaSplashDialog = null; } } @Override public boolean onPrepareOptionsMenu(Menu menu) { if (mPageComponent != null) { menu.clear(); MenuRepresentation menuRepresentation = MonacaApplication.findMenuRepresentation(mPageComponent.menuName); if (menuRepresentation != null) { menuRepresentation.configureMenu(uiContext, menu); } return true; } else { menu.clear(); MenuRepresentation menuRepresentation = MonacaApplication.findMenuRepresentation("default"); if (menuRepresentation != null) { menuRepresentation.configureMenu(uiContext, menu); } return true; } } protected void prepare() { Bundle bundle = getIntent().getExtras(); if (bundle != null) { Serializable s = bundle.getSerializable(GCMPushDataset.KEY); if (s != null) { pushData = (GCMPushDataset) s; } } loadParams(); MonacaApplication.addPage(this); pageIndex = MonacaApplication.getPages().size() - 1; AppJsonSetting appJsonSetting = mApp.getAppJsonSetting(); boolean autoHide = false; if (appJsonSetting != null) { autoHide = appJsonSetting.getAutoHide(); } if (pageIndex == 0 && autoHide == false) { showMonacaSplash(); } registerReceiver(closePageReceiver, ClosePageIntent.createIntentFilter()); uiContext = new UIContext(getCurrentUriWithoutOptions(), this); // override theme if (transitionParams.animationType == TransitionParams.TransitionAnimationType.NONE) { } else if (transitionParams.animationType == TransitionParams.TransitionAnimationType.MODAL) { setTheme(mobi.monaca.framework.psedo.R.style.MonacaDialogTheme); } else if (transitionParams.animationType == TransitionParams.TransitionAnimationType.SLIDE_LEFT) { setTheme(mobi.monaca.framework.psedo.R.style.MonacaSlideTheme); } else { } try { infoForJavaScript.put("display", createDisplayInfo()); } catch (JSONException e) { throw new RuntimeException(e); } } /** Load background drawable from transition params and device orientation. */ protected void loadBackground(Configuration config) { if (transitionParams != null && transitionParams.hasBackgroundImage()) { String path = null; String preferedPath = "www/" + UIContext.getPreferredPath(transitionParams.backgroundImagePath); if (AssetUtils.existsAsset(this, preferedPath)) { path = preferedPath; } else { path = "www/" + transitionParams.backgroundImagePath; } try { Bitmap bitmap = BitmapFactory.decodeStream(LocalFileBootloader.openAsset(this.getApplicationContext(), path)); background = new BackgroundDrawable(bitmap, getWindowManager().getDefaultDisplay(), config.orientation); } catch (Exception e) { MyLog.e(TAG, e.getMessage()); } } } /** Release background drawable. */ protected void unloadBackground() { if (background != null) { appView.setBackgroundDrawable(null); root.setBackgroundDrawable(null); background.setCallback(null); background = null; System.gc(); } } public void initMonaca() { appView.setFocusable(true); appView.setFocusableInTouchMode(true); /* * to initialize cordova webView, MonacaWebView detects this symbol and * passes "javascript:" to CordovaWebView#loadUrlIntoView and * MonacaPageActivity supresses timeout error message caused by this */ this.loadUrl(MonacaWebView.INITIALIZATION_REQUEST_URL); root.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { try { int height = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getHeight() - root.getHeight(); infoForJavaScript.put("statusbarHeight", height); } catch (JSONException e) { MyLog.e(getClass().getSimpleName(), "fail to get statusbar height."); } } }); // for focus problem between native component and webView appView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_UP: if (!v.hasFocus()) { v.requestFocus(); } break; } return false; } }); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override public void init() { CordovaWebView webView = new MonacaWebView(this); // Fix webview bug on ICS_MR1 where webview background is always white // when hardware accerleration is on if (VERSION.SDK_INT == VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); } if (uiContext.getSettings().forceDisableWebviewGPU) { Method method; try { method = webView.getClass().getMethod("setLayerType", new Class[] { int.class, Paint.class }); method.invoke(webView, new Object[] { View.LAYER_TYPE_SOFTWARE, null }); } catch (Exception e) { MyLog.e(TAG, "webview.setLayerType() is fail."); } } WebSettings webSettings = webView.getSettings(); webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH); CordovaWebViewClient webViewClient = (CordovaWebViewClient) createWebViewClient(this, webView); MonacaChromeClient webChromeClient = new MonacaChromeClient(this, webView); this.init(webView, webViewClient, webChromeClient); this.initMonaca(); } /** Setup background drawable for app View and root view. */ public void setupBackground(Drawable background) { appView.setBackgroundColor(0x00000000); if (background != null) { if (appView != null) { appView.setBackgroundDrawable(background); } if (root != null) { root.setBackgroundDrawable(background); if (root.getParent() == null) { setContentView(root); } } } else { if (appView != null) { // Default background appView.setBackgroundResource(color.white); } } } protected void loadLayoutInformation() { appView.loadUrl("javascript: window.__layout = " + infoForJavaScript.toString()); } protected JSONObject createDisplayInfo() { JSONObject result = new JSONObject(); DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); Display display = getWindowManager().getDefaultDisplay(); try { result.put("width", display.getWidth()); result.put("height", display.getHeight()); } catch (JSONException e) { } return result; } protected void loadParams() { Intent intent = getIntent(); transitionParams = (TransitionParams) intent.getSerializableExtra(TRANSITION_PARAM_NAME); if (transitionParams == null) { transitionParams = TransitionParams.createDefaultParams(this.getRequestedOrientation()); } String startPage = intent.hasExtra(URL_PARAM_NAME) ? intent.getStringExtra(URL_PARAM_NAME) : "file:///android_asset/www/index.html"; if (shouldLoadExtractedIndex()) { startPage = "file://" + LocalFileBootloader.getFullPath(getContext(), "www/index.html"); } setCurrentUri(startPage); } // called by MonacaHttpServer to know the root folder of the app. // in this case, we tell it to use the assets folder public String getAppWWWPath() { return getApplicationInfo().dataDir + "/www/"; } protected boolean shouldLoadExtractedIndex() { return !getIntent().hasExtra(URL_PARAM_NAME) && (mApp.getAppJsonSetting().shouldExtractAssets() || mApp.needToUseBootloader()); } public JSONObject getInfoForJavaScript() { return infoForJavaScript; } /** Load local ui file */ public void loadUiFile(String uri) { JSONObject uiJSON = getUIJSON(uri); if (uiJSON != null) { try { mPageComponent = new PageComponent(uiContext, uiJSON); } catch (Exception e) { e.printStackTrace(); LogItem logItem = new LogItem(TimeStamp.getCurrentTimeStamp(), Source.SYSTEM, LogLevel.ERROR, "NativeComponent:" + e.getMessage(), "", 0); MyLog.sendBroadcastDebugLog(getContext(), logItem); return; } } applyUiToView(); } protected JSONObject getUIJSON(String uri) { String uiJSONString = null; try { uiJSONString = getUIFile(UrlUtil.getUIFileUrl(uri)); } catch (IOException e1) { MyLog.d(TAG, "UI file not found"); return null; } JSONObject uiJSON; try { uiJSON = new JSONObject(uiJSONString); return uiJSON; } catch (JSONException e) { e.printStackTrace(); UIUtil.reportJSONParseError(getApplicationContext(), e.getMessage()); return null; } } public void applyScreenOrientation(PageOrientation pageOrientation) { if (pageOrientation == null) { MyLog.v(TAG, "null -> apply from manifest"); applyScreenOrientationFromManifest(); return; } switch (pageOrientation) { case PORTRAIT: setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); break; case LANDSCAPE: setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); break; case SENSOR: setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); break; case INHERIT: // no override. Do nothing. MyLog.v(TAG, "inherit -> apply from manifest"); applyScreenOrientationFromManifest(); break; default: break; } } protected void applyScreenOrientationFromManifest() { try { PackageInfo packageInfo = this.getPackageManager().getPackageInfo(this.getPackageName(), PackageManager.GET_ACTIVITIES); int screenOrientation = getScreenOrientationOfMonacaPageActivity(packageInfo); setRequestedOrientation(screenOrientation); } catch (NameNotFoundException e) { e.printStackTrace(); } } private int getScreenOrientationOfMonacaPageActivity(PackageInfo packageInfo) { ActivityInfo[] activies = packageInfo.activities; if (activies != null) { for (int i = 0; i < activies.length; i++) { ActivityInfo activityInfo = activies[i]; if (activityInfo.name.equalsIgnoreCase(MonacaPageActivity.class.getName())) { MyLog.v(TAG, "found screenorientation for MonacaPageAcitivyt"); return activityInfo.screenOrientation; } } } // not found -> use sensor return ActivityInfo.SCREEN_ORIENTATION_SENSOR; } protected void applyUiToView() { if (mPageComponent == null) { applyScreenOrientationFromManifest(); return; } setupBackground(mPageComponent.getBackgroundDrawable()); applyScreenOrientation(mPageComponent.getScreenOrientation()); // clean up root.removeAllViews(); ViewGroup appViewParent = ((ViewGroup) appView.getParent()); if (appViewParent != null) { appViewParent.removeAllViews(); } RelativeLayout container = new RelativeLayout(this); root.addView(container, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)); // top ToolbarContainer topComponent = (ToolbarContainer) mPageComponent.getTopComponent(); int topComponentViewId = 0; if (topComponent != null) { // view topComponentViewId = topComponent.getView().getId(); RelativeLayout.LayoutParams topComponentParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); topComponentParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); container.addView(topComponent.getView(), topComponentParams); // shadow RelativeLayout.LayoutParams shadowViewParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); shadowViewParams.addRule(RelativeLayout.BELOW, topComponentViewId); container.addView(topComponent.getShadowView(), shadowViewParams); } // bottom Container bottomComponentContainer = (Container) mPageComponent.getBottomComponent(); int bottomComponentContainerViewId = 0; if (bottomComponentContainer != null) { bottomComponentContainerViewId = bottomComponentContainer.getView().getId(); RelativeLayout.LayoutParams bottomComponentParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); bottomComponentParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); container.addView(bottomComponentContainer.getView(), bottomComponentParams); // shadow RelativeLayout.LayoutParams shadowViewParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); shadowViewParams.addRule(RelativeLayout.ABOVE, bottomComponentContainerViewId); container.addView(bottomComponentContainer.getShadowView(), shadowViewParams); } // webview RelativeLayout.LayoutParams webViewParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); webViewParams.alignWithParent = true; if (topComponent != null && topComponent.isTransparent()) { webViewParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); } else { webViewParams.addRule(RelativeLayout.BELOW, topComponentViewId); } if (bottomComponentContainer != null) { if (bottomComponentContainer.isTransparent()) { webViewParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); } webViewParams.addRule(RelativeLayout.ABOVE, bottomComponentContainerViewId); } container.addView(appView, 0, webViewParams); } public UIContext getUiContext() { return uiContext; } protected String getUIFile(String path) throws IOException { Reader reader; InputStream stream = null; if (path == null) { return ""; } MyLog.d(getClass().getSimpleName(), "ui file loading: " + path); if (path.startsWith("file:///android_asset/")) { stream = LocalFileBootloader.openAsset(this.getApplicationContext(), path.substring("file:///android_asset/".length())); reader = new InputStreamReader(stream); } else if (path.startsWith("file://")) { path = new File(path.substring(7)).getCanonicalPath(); reader = new FileReader(new File(path)); } else { stream = InputStreamLoader.loadAssetFile(this, path); reader = new InputStreamReader(stream, "UTF-8"); } Writer writer = new StringWriter(); char[] buffer = new char[1024]; try { int n; while ((n = reader.read(buffer)) != -1) { writer.write(buffer, 0, n); } } finally { reader.close(); if (stream != null) { stream.close(); } } return writer.toString(); } /** Retrieve a style of Native UI Framework component. */ public JSONObject getStyle(String componentId) { if (mPageComponent.getComponentIDsMap().containsKey(componentId)) { Component component = mPageComponent.getComponentIDsMap().get(componentId); return component.getStyle(); } return null; } /** Update a style of Native UI Framework component. */ public void updateStyle(final UpdateStyleQuery query) { List<UpdateStyleQuery> queries = new ArrayList<UpdateStyleQuery>(); queries.add(query); updateStyleBulkily(queries); } /** Update bulkily the styles of Native UI Framework components. */ public void updateStyleBulkily(final List<UpdateStyleQuery> queries) { handler.post(new Runnable() { @Override public void run() { for (UpdateStyleQuery query : queries) { for (int i = 0; i < query.ids.length(); i++) { String componentId = query.ids.optString(i, ""); if (mPageComponent != null && mPageComponent.getComponentIDsMap() != null && mPageComponent.getComponentIDsMap().containsKey(componentId)) { Component component = mPageComponent.getComponentIDsMap().get(componentId); if (component != null) { try { component.updateStyle(query.style); } catch (NativeUIException e) { e.printStackTrace(); } } else { Log.e(MonacaPageActivity.class.getSimpleName(), "update fail => id: " + componentId + ", style: " + query.style.toString()); } } else { Log.e(MonacaPageActivity.class.getSimpleName(), "no such component id: " + componentId); } } } MyLog.d(MonacaPageActivity.class.getSimpleName(), "updateStyleBulkily() done"); } }); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { BenchmarkTimer.mark("visible"); BenchmarkTimer.finish(); requestJStoProcessMessages(); } } /* * current Native2JS bridge use window.online event to signal to js side to * process message. there is a bug that when resumed from other page * activity, the online/offline event is not triggered it will trigger if * there is more than one js statment in the queue -> we queue a dummy * console.log */ private void requestJStoProcessMessages() { appView.sendJavascript("void(0);"); } public void onPageFinished(View view, String url) { // BenchmarkTimer.mark("page finish:" + url); // if(!url.startsWith("about:blank")){ // BenchmarkTimer.finish(); // } // for android4's strange bug. requestJStoProcessMessages(); // check if this is 404 page String errorUrl = getIntent().getStringExtra("error_url"); processMonacaReady(url); if (errorUrl != null && url.endsWith("/404/404.html")) { String backButtonText = getString(R.string.back_button_text); errorUrl = UrlUtil.cutHostInUri(errorUrl); MyLog.v(TAG, "error url:" + errorUrl); appView.loadUrl("javascript:$('#url').html(\"" + errorUrl + "\"); $('#backButton').html('" + backButtonText + "')"); } if (url.equals(getCurrentUriWithoutOptions()) && UrlUtil.isMonacaUri(this, url) && currentMonacaUri.hasUnusedFragment()) { // process pushed url fragment // TODO refactor MonacaURI not to use this checkment.noncritical bug // with nativecomponent remains that does not work with // nativecomponent remains. appView.loadUrl("javascript:window.location.hash = '" + currentMonacaUri.popFragment() + "';"); } } public void onPageStarted(View view, String url) { ViewGroup.LayoutParams params = this.appView.getLayoutParams(); params.width = ViewGroup.LayoutParams.MATCH_PARENT; params.height = ViewGroup.LayoutParams.MATCH_PARENT; this.appView.setLayoutParams(params); } @Override protected void onRestart() { MyLog.i(TAG, "onRestart"); super.onRestart(); loadBackground(getResources().getConfiguration()); if (background != null) { background.invalidateSelf(); } } @Override protected void onResume() { MyLog.i(TAG, "onResume"); try { WebView.class.getMethod("onResume").invoke(this); } catch (Exception e) { } isCapableForTransition = true; mApp.showMonacaSpinnerDialogIfAny(); super.onResume(); //fix broken native component in some device. root.invalidate(); } private void triggerOnReactivate() { if (appView != null && appView.pluginManager != null) { appView.loadUrl("javascript: window.onReactivate && onReactivate();"); } } @Override protected void onPause() { MyLog.i(TAG, "onPause"); super.onPause(); this.removeMonacaSplash(); mApp.hideMonacaSpinnerDialog(); if (isFinishing()) { onDestroyMonacaCaller(); } } /** * @see MonacaPageActivity#onDestroyMonaca() */ private final void onDestroyMonacaCaller() { // MyLog.d(TAG, "monacaOnDestroyCaller()"); if (!isOnDestroyMonacaCalled) { // prevent from multiple calls onDestroyMonaca(); isOnDestroyMonacaCalled = true; } } /** * to call onDestroy surely, this is called in onPause or onDestroy since * Activity#finish doesn't guarantee calling onDestroy. this should be * called through onDestroyMonacaCaller * * @see MonacaPageActivity#onDestroyMonacaCaller() */ protected void onDestroyMonaca() { MyLog.d(TAG, "onDestroyMonaca"); isCapableForTransition = false; unregisterReceiver(pushReceiver); appView.setBackgroundDrawable(null); root.setBackgroundDrawable(null); this.removeMonacaSplash(); MonacaApplication.removePage(this); unregisterReceiver(closePageReceiver); if (background != null) { background.setCallback(null); background = null; } if (mPageComponent != null) { mPageComponent.getComponentIDsMap().clear(); mPageComponent = null; } appView.setBackgroundDrawable(null); root.setBackgroundDrawable(null); closePageReceiver = null; root.removeView(appView); appView.stopLoading(); // appView.setWebChromeClient(null); // this caused Android 2.3.5 to // crash. Null Pointer Exception appView.setWebViewClient(null); // this causes null pointer on some devices // for DroidGap posts delayed message to appView // unregisterForContextMenu(appView); // appView.destroy(); // appView = null; } @Override public void onDestroy() { MyLog.i(TAG, "onDestroy"); onDestroyMonacaCaller(); super.onDestroy(); } /** Reload current URI. */ public void reload() { appView.stopLoading(); loadUri(getCurrentUriWithoutOptions(), false); } public String getCurrentHtml() { return mCurrentHtml; } protected String buildCurrentUriHtml() throws IOException { String html = AssetUtils.assetToString(this, getCurrentUriWithoutOptions()); if (UrlUtil.isMonacaUri(this, currentMonacaUri.getOriginalUrl()) && currentMonacaUri.hasQueryParams()) { html = currentMonacaUri.getQueryParamsContainingHtml(html); } return html; } /** Load current URI. */ public void loadUri(String uri, final boolean withoutUIFile) { setCurrentUri(uri); String currentUriWithoutQuery = getCurrentUriWithoutOptions(); MyLog.v(TAG, "loadUri() uri:" + currentUriWithoutQuery); // check for 404 if (currentUriWithoutQuery.equalsIgnoreCase("file:///android_asset/www/404/404.html")) { String failingUrl = getIntent().getStringExtra("error_url"); show404Page(failingUrl); return; } if (!withoutUIFile) { mPageComponent = null; loadUiFile(getCurrentUriWithoutOptions()); } try { mCurrentHtml = buildCurrentUriHtml(); appView.loadDataWithBaseURL(getCurrentUriWithoutOptions(), mCurrentHtml, "text/html", "UTF-8", this.getCurrentUriWithoutOptions()); } catch (IOException e) { MyLog.w(TAG, "Maybe Not MonacaURI : " + e.getMessage()); MyLog.w(TAG, "load as nomal url:" + currentUriWithoutQuery); if (uri.startsWith("file://")) { show404Page(uri); return; } appView.setBackgroundColor(0x00000000); loadLayoutInformation(); appView.loadUrl(currentMonacaUri.getOriginalUrl()); appView.clearView(); appView.invalidate(); } } public void show404Page(String failingUrl) { try { InputStream is = getResources().openRawResource(R.raw.error404); String html = IOUtils.toString(is); html = html.replaceFirst("url_place_holder", UrlUtil.cutHostInUri(failingUrl)); html = html.replaceFirst("back_button_text", getString(R.string.back_button_text)); appView.loadDataWithBaseURL("file:///android_res/raw/error404.html", html, "text/html", "utf-8", null); } catch (IOException e) { MyLog.e(TAG, e.getMessage()); } } public void push404Page(String errorUrl) { Intent intent = new Intent(this, getClass()); intent.putExtra(URL_PARAM_NAME, "file:///android_asset/www/404/404.html"); intent.putExtra("error_url", errorUrl); TransitionParams params = TransitionParams.from(new JSONObject(), "none"); intent.putExtra(TRANSITION_PARAM_NAME, params); startActivity(intent); finish(); } public void pushPageWithIntent(String url, TransitionParams params) { if (isCapableForTransition) { Intent intent = createIntentForNextPage(url, params); isCapableForTransition = false; startActivityForResult(intent, MONACA_TRANSIT_REQUEST); if (params.needsToClearStack()) { /* * intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) works * similarly but shows blank screen in transit animation */ new Handler().postDelayed(new Runnable() { @Override public void run() { finish(); } }, 500); } } } protected Intent createIntentForNextPage(String url, TransitionParams params) { Intent intent = new Intent(this, getClass()); intent.putExtra(URL_PARAM_NAME, UrlUtil.getResolvedUrl(url)); if (params != null) { intent.putExtra(TRANSITION_PARAM_NAME, params); } return intent; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { if (hasOnTapBackButtonAction()) { mPageComponent.eventer.onTapBackButton(); } else if (hasBackButtonEventer()) { mPageComponent.getBackButtonEventer().onTap(); } else if (appView.isBackButtonBound()) { return super.onKeyDown(keyCode, event); } else { popPage(); } return true; } else { return super.onKeyDown(keyCode, event); } } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && (hasBackButtonEventer() || hasOnTapBackButtonAction())) { return true; } else { return super.onKeyUp(keyCode, event); } } public boolean hasBackButtonEventer() { return mPageComponent != null && mPageComponent.getBackButtonEventer() != null; } public boolean hasOnTapBackButtonAction() { return mPageComponent != null && mPageComponent.eventer != null && mPageComponent.eventer.hasOnTapBackButtonAction(); } public void pushPageAsync(String relativePath, final TransitionParams params) { final String url = getCurrentUriWithoutOptions() + "/../" + relativePath; BenchmarkTimer.start(); BenchmarkTimer.mark("pushPageAsync"); handler.postAtFrontOfQueue(new Runnable() { @Override public void run() { BenchmarkTimer.mark("monaca.pushPageAsync.run"); pushPageWithIntent(url, params); } }); } public void popPage() { int pageNum = MonacaApplication.getPages().size(); finish(); if (pageNum > 1) { // dirty fix for android4's strange bug if (transitionParams.animationType == TransitionParams.TransitionAnimationType.MODAL) { overridePendingTransition(mobi.monaca.framework.psedo.R.anim.monaca_dialog_close_enter, mobi.monaca.framework.psedo.R.anim.monaca_dialog_close_exit); } else if (transitionParams.animationType == TransitionParams.TransitionAnimationType.SLIDE_LEFT) { overridePendingTransition(mobi.monaca.framework.psedo.R.anim.monaca_slide_close_enter, mobi.monaca.framework.psedo.R.anim.monaca_slide_close_exit); } else if (transitionParams.animationType == TransitionParams.TransitionAnimationType.SLIDE_RIGHT) { overridePendingTransition(mobi.monaca.framework.psedo.R.anim.monaca_slide_right_close_enter, mobi.monaca.framework.psedo.R.anim.monaca_slide_right_close_exit); } else if (transitionParams.animationType == TransitionParams.TransitionAnimationType.NONE) { overridePendingTransition(mobi.monaca.framework.psedo.R.anim.monaca_none, mobi.monaca.framework.psedo.R.anim.monaca_none); } } } public void _popPage() { int pageNum = MonacaApplication.getPages().size(); finish(); if (pageNum > 1) { // dirty fix for android4's strange bug overridePendingTransition(mobi.monaca.framework.psedo.R.anim.monaca_slide_close_enter, mobi.monaca.framework.psedo.R.anim.monaca_slide_close_exit); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { if (requestCode == MONACA_TRANSIT_REQUEST) { triggerOnReactivate(); } else { super.onActivityResult(requestCode, resultCode, intent); } } @Override public void finishActivityFromChild(Activity child, int requestCode) { MyLog.e(TAG, "finish activity from child. child class " + child.getClass().getSimpleName() + ", request code: " + requestCode); super.finishActivityFromChild(child, requestCode); } @Override public void finishFromChild(Activity child) { MyLog.e(TAG, "finish from child. child class " + child.getClass().getSimpleName()); super.finishFromChild(child); } public void dismissPage() { int pageNum = MonacaApplication.getPages().size(); finish(); if (pageNum > 1) { overridePendingTransition(mobi.monaca.framework.psedo.R.anim.monaca_dialog_close_enter, mobi.monaca.framework.psedo.R.anim.monaca_dialog_close_exit); } } public void popPageAsync(final TransitionParams params) { handler.postAtFrontOfQueue(new Runnable() { @Override public void run() { if (params.animationType == TransitionParams.TransitionAnimationType.POP) { _popPage(); } else if (params.animationType == TransitionParams.TransitionAnimationType.DISMISS) { dismissPage(); } else { _popPage(); } } }); } public void goHomeAsync(JSONObject options) { final String homeUrl = getHomeUrl(options); handler.post(new Runnable() { @Override public void run() { int numPages = MonacaApplication.getPages().size(); for (int i = numPages - 1; i > 0; i--) { MonacaPageActivity page = MonacaApplication.getPages().get(i); page.finish(); } } }); } protected WebViewClient createWebViewClient(MonacaPageActivity page, CordovaWebView webView) { MonacaPageGingerbreadWebViewClient client = null; if (Integer.valueOf(android.os.Build.VERSION.SDK_INT) < 11) { client = new MonacaPageGingerbreadWebViewClient(page, webView); } else { client = new MonacaPageHoneyCombWebViewClient(page, webView); } return client; } protected String getHomeUrl(JSONObject options) { if (options == null) { return "file:///android_asset/www/index.html"; } return options.optString("url", "").equals("") ? "file:///android_asset/www/index.html" : getCurrentUriWithoutOptions() + "/../" + options.optString("url"); } @Override protected void onStop() { MyLog.d(TAG, "onStop"); super.onStop(); unloadBackground(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); MyLog.d(getClass().getSimpleName(), "onConfigurationChanged()"); // handling orieantation change for background image. if (background != null) { loadBackground(newConfig); // setupBackground(); if (background != null) { background.invalidateSelf(); } } uiContext.fireOnRotateListeners(newConfig.orientation); // appView.clearView(); commented out cos it cause the webview to show // nothing appView.invalidate(); Display display = getWindowManager().getDefaultDisplay(); MyLog.d(getClass().getSimpleName(), "metrics width: " + display.getWidth() + ", height: " + display.getHeight()); } /* * Called from WebViewClient -> can be used in DeubggerPageActivity to * publish log message */ public void onLoadResource(WebView view, String url) { } protected void processMonacaReady(String url) { if (pushData != null) { if (UrlUtil.isMonacaUri(this, url)) { sendPushToWebView(pushData); pushData = null; } } else { MyLog.d(TAG, "no Push"); } } protected void sendPushToWebView(GCMPushDataset pushData) { appView.loadUrl("javascript:monaca.cloud.Push.send(" + pushData.getExtraJSONString() + ")"); } public MonacaURI getCurrentMonacaUri() { return currentMonacaUri; } public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { } @Override public void onReceivedError(int errorCode, String description, String failingUrl) { if (isInitializationMessage(errorCode, description, failingUrl)) { MyLog.d(TAG, "supressed initialize message"); return; } else { super.onReceivedError(errorCode, description, failingUrl); } } protected boolean isInitializationMessage(int errorCode, String description, String failingUrl) { return (errorCode == MonacaWebView.INITIALIZATION_ERROR_CODE && description.contains(MonacaWebView.INITIALIZATION_DESCRIPTION) && failingUrl .startsWith(MonacaWebView.INITIALIZATION_MADIATOR)); } public String getCurrentUriWithoutOptions() { return currentMonacaUri.getUrlWithoutOptions(); } /** * update uri and currentMonacaURI * * @param uri */ public void setCurrentUri(String uri) { MyLog.v(TAG, "setCurrentUri:" + uri); currentMonacaUri = new MonacaURI(uri); uiContext = new UIContext(getCurrentUriWithoutOptions(), this); } }