import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.lang.ref.WeakReference;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
+import org.chromium.base.ApplicationStatus.ActivityStateListener;
+import org.chromium.base.CommandLine;
import org.xwalk.core.internal.extension.XWalkExtensionManager;
import org.xwalk.core.internal.extension.XWalkPathHelper;
* {@link XWalkUIClientInternal} for listening to the events related to resource loading and UI.
* By default, Crosswalk has a default implementation. Callers can override them if needed.</p>
*
- * <p>Unlike other Android views, this class has to listen to system events like application life
- * cycle, intents, and activity result. The web engine inside this view need to get and handle
- * them. And the onDestroy() method of XWalkViewInternal MUST be called explicitly when an XWalkViewInternal
- * won't be used anymore, otherwise it will cause the memory leak from the native side of the web
- * engine. It's similar to the
- * <a href="http://developer.android.com/reference/android/webkit/WebView.html#destroy()">
- * destroy()</a> method of Android WebView. For example:</p>
+ * <p>Unlike other Android views, this class has to listen to system events like intents and activity result.
+ * The web engine inside this view need to get and handle them.
+ * With contianer activity's lifecycle change, XWalkViewInternal will pause all timers and other
+ * components like videos when activity paused, resume back them when activity resumed.
+ * When activity is about to destroy, XWalkViewInternal will destroy itself as well.
+ * Embedders can also call onHide() and pauseTimers() to explicitly pause XWalkViewInternal.
+ * Similarily with onShow(), resumeTimers() and onDestroy().
+ *
+ * For example:</p>
*
* <pre>
* import android.app.Activity;
* }
*
* @Override
- * protected void onPause() {
- * super.onPause();
- * if (mXwalkView != null) {
- * mXwalkView.pauseTimers();
- * mXwalkView.onHide();
- * }
- * }
- *
- * @Override
- * protected void onResume() {
- * super.onResume();
- * if (mXwalkView != null) {
- * mXwalkView.resumeTimers();
- * mXwalkView.onShow();
- * }
- * }
- *
- * @Override
- * protected void onDestroy() {
- * super.onDestroy();
- * if (mXwalkView != null) {
- * mXwalkView.onDestroy();
- * }
- * }
- *
- * @Override
* protected void onActivityResult(int requestCode, int resultCode, Intent data) {
* if (mXwalkView != null) {
* mXwalkView.onActivityResult(requestCode, resultCode, data);
* }
* </pre>
*/
+@XWalkAPI(extendClass = FrameLayout.class, createExternally = true)
public class XWalkViewInternal extends android.widget.FrameLayout {
+ private class XWalkActivityStateListener implements ActivityStateListener {
+ WeakReference<XWalkViewInternal> mXWalkViewRef;
+
+ XWalkActivityStateListener(XWalkViewInternal view) {
+ mXWalkViewRef = new WeakReference<XWalkViewInternal>(view);
+ }
+
+ @Override
+ public void onActivityStateChange(Activity activity, int newState) {
+ XWalkViewInternal view = mXWalkViewRef.get();
+ if (view == null) return;
+ view.onActivityStateChange(activity, newState);
+ }
+ }
+
static final String PLAYSTORE_DETAIL_URI = "market://details?id=";
private XWalkContent mContent;
private Context mContext;
private XWalkExtensionManager mExtensionManager;
private boolean mIsHidden;
+ private XWalkActivityStateListener mActivityStateListener;
/**
* Normal reload mode as default.
* @since 1.0
*/
+ @XWalkAPI
public static final int RELOAD_NORMAL = 0;
/**
* Reload mode with bypassing the cache.
* @since 1.0
*/
+ @XWalkAPI
public static final int RELOAD_IGNORE_CACHE = 1;
/**
* @param attrs an AttributeSet passed to our parent.
* @since 1.0
*/
+ @XWalkAPI(preWrapperLines = {
+ " super(${param1}, ${param2});"},
+ postWrapperLines = {
+ " addView((FrameLayout)bridge, new FrameLayout.LayoutParams(",
+ " FrameLayout.LayoutParams.MATCH_PARENT,",
+ " FrameLayout.LayoutParams.MATCH_PARENT));"})
public XWalkViewInternal(Context context, AttributeSet attrs) {
super(context, attrs);
* @param activity the activity for this XWalkViewInternal.
* @since 1.0
*/
+ @XWalkAPI(preWrapperLines = {
+ " super(${param1}, null);"},
+ postWrapperLines = {
+ " addView((FrameLayout)bridge, new FrameLayout.LayoutParams(",
+ " FrameLayout.LayoutParams.MATCH_PARENT,",
+ " FrameLayout.LayoutParams.MATCH_PARENT));"})
public XWalkViewInternal(Context context, Activity activity) {
super(context, null);
checkThreadSafety();
// Intialize library, paks and others.
try {
XWalkViewDelegate.init(this);
+ mActivityStateListener = new XWalkActivityStateListener(this);
+ ApplicationStatus.registerStateListenerForActivity(
+ mActivityStateListener, getActivity());
} catch (Throwable e) {
// Try to find if there is UnsatisfiedLinkError in the cause chain of the met Throwable.
Throwable linkError = e;
setNavigationHandler(new XWalkNavigationHandlerImpl(context));
setNotificationService(new XWalkNotificationServiceImpl(context, this));
- // Enable xwalk extension mechanism and start load extensions here.
- // Note that it has to be after above initialization.
- mExtensionManager = new XWalkExtensionManager(context, getActivity());
- mExtensionManager.loadExtensions();
+ if (!CommandLine.getInstance().hasSwitch("disable-xwalk-extensions")) {
+ // Enable xwalk extension mechanism and start load extensions here.
+ // Note that it has to be after above initialization.
+ mExtensionManager = new XWalkExtensionManager(context, getActivity());
+ mExtensionManager.loadExtensions();
+ }
XWalkPathHelper.initialize();
XWalkPathHelper.setCacheDirectory(
* @param content the content for the web page/app. Could be empty.
* @since 1.0
*/
+ @XWalkAPI
public void load(String url, String content) {
if (mContent == null) return;
checkThreadSafety();
* @param content the content for manifest.json.
* @since 1.0
*/
+ @XWalkAPI
public void loadAppFromManifest(String url, String content) {
if (mContent == null) return;
checkThreadSafety();
* @param mode the reload mode.
* @since 1.0
*/
+ @XWalkAPI
public void reload(int mode) {
if (mContent == null) return;
checkThreadSafety();
* Stop current loading progress.
* @since 1.0
*/
+ @XWalkAPI
public void stopLoading() {
if (mContent == null) return;
checkThreadSafety();
* @return the url for current web page/app.
* @since 1.0
*/
+ @XWalkAPI
public String getUrl() {
if (mContent == null) return null;
checkThreadSafety();
* @return the title for current web page/app.
* @since 1.0
*/
+ @XWalkAPI
public String getTitle() {
if (mContent == null) return null;
checkThreadSafety();
* @return the original url.
* @since 1.0
*/
+ @XWalkAPI
public String getOriginalUrl() {
if (mContent == null) return null;
checkThreadSafety();
* @return the navigation history.
* @since 1.0
*/
+ @XWalkAPI
public XWalkNavigationHistoryInternal getNavigationHistory() {
if (mContent == null) return null;
checkThreadSafety();
* @param name the name injected in JavaScript.
* @since 1.0
*/
+ @XWalkAPI
public void addJavascriptInterface(Object object, String name) {
if (mContent == null) return;
checkThreadSafety();
* @param callback the callback to handle the evaluated result.
* @since 1.0
*/
+ @XWalkAPI
public void evaluateJavascript(String script, ValueCallback<String> callback) {
if (mContent == null) return;
checkThreadSafety();
* @param includeDiskFiles indicate whether to clear disk files for cache.
* @since 1.0
*/
+ @XWalkAPI
public void clearCache(boolean includeDiskFiles) {
if (mContent == null) return;
checkThreadSafety();
* @return true if any HTML element is occupying the whole screen.
* @since 1.0
*/
+ @XWalkAPI
public boolean hasEnteredFullscreen() {
if (mContent == null) return false;
checkThreadSafety();
* in fullscreen.
* @since 1.0
*/
+ @XWalkAPI
public void leaveFullscreen() {
if (mContent == null) return;
checkThreadSafety();
/**
* Pause all layout, parsing and JavaScript timers for all XWalkViewInternal instances.
- * Typically it should be called when the activity for this view is paused,
- * and accordingly {@link #resumeTimers} should be called when the activity
- * is resumed again.
+ * It will be called when the container Activity get paused. It can also be explicitly
+ * called to pause timers.
*
* Note that it will globally impact all XWalkViewInternal instances, not limited to
* just this XWalkViewInternal.
*
* @since 1.0
*/
+ @XWalkAPI
public void pauseTimers() {
if (mContent == null) return;
checkThreadSafety();
/**
* Resume all layout, parsing and JavaScript timers for all XWalkViewInternal instances.
- * Typically it should be called when the activity for this view is resumed.
+ * It will be called when the container Activity get resumed. It can also be explicitly
+ * called to resume timers.
*
* Note that it will globally impact all XWalkViewInternal instances, not limited to
* just this XWalkViewInternal.
*
* @since 1.0
*/
+ @XWalkAPI
public void resumeTimers() {
if (mContent == null) return;
checkThreadSafety();
* Pause many other things except JavaScript timers inside rendering engine,
* like video player, modal dialogs, etc. See {@link #pauseTimers} about pausing
* JavaScript timers.
- * Typically it should be called when the activity for this view is paused.
+ * It will be called when the container Activity get paused. It can also be explicitly
+ * called to pause above things.
* @since 1.0
*/
+ @XWalkAPI
public void onHide() {
if (mContent == null || mIsHidden) return;
- mExtensionManager.onPause();
+ if (null != mExtensionManager) mExtensionManager.onPause();
mContent.onPause();
mIsHidden = true;
}
* Resume video player, modal dialogs. Embedders are in charge of calling
* this during resuming this activity if they call onHide.
* Typically it should be called when the activity for this view is resumed.
+ * It will be called when the container Activity get resumed. It can also be explicitly
+ * called to resume above things.
* @since 1.0
*/
+ @XWalkAPI
public void onShow() {
if (mContent == null || !mIsHidden ) return;
- mExtensionManager.onResume();
+ if (null != mExtensionManager) mExtensionManager.onResume();
mContent.onResume();
mIsHidden = false;
}
/**
* Release internal resources occupied by this XWalkViewInternal.
+ * It will be called when the container Activity get destroyed. It can also be explicitly
+ * called to release resources.
* @since 1.0
*/
+ @XWalkAPI
public void onDestroy() {
destroy();
}
* @param data passed from android.app.Activity.onActivityResult().
* @since 1.0
*/
+ @XWalkAPI
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mContent == null) return;
- mExtensionManager.onActivityResult(requestCode, resultCode, data);
+ if (null != mExtensionManager)
+ mExtensionManager.onActivityResult(requestCode, resultCode, data);
mContent.onActivityResult(requestCode, resultCode, data);
}
* @param intent passed from android.app.Activity.onNewIntent().
* @since 1.0
*/
+ @XWalkAPI
public boolean onNewIntent(Intent intent) {
if (mContent == null) return false;
return mContent.onNewIntent(intent);
* @param outState the saved state for restoring.
* @since 1.0
*/
+ @XWalkAPI
public boolean saveState(Bundle outState) {
if (mContent == null) return false;
mContent.saveState(outState);
* @return true if it can restore the state.
* @since 1.0
*/
+ @XWalkAPI
public boolean restoreState(Bundle inState) {
if (mContent == null) return false;
if (mContent.restoreState(inState) != null) return true;
* @since 1.0
*/
// TODO(yongsheng): make it static?
+ @XWalkAPI
public String getAPIVersion() {
- return "2.0";
+ return "2.1";
}
/**
* @since 1.0
*/
// TODO(yongsheng): make it static?
+ @XWalkAPI
public String getXWalkVersion() {
if (mContent == null) return null;
return mContent.getXWalkVersion();
* @param client the XWalkUIClientInternal defined by callers.
* @since 1.0
*/
+ @XWalkAPI
public void setUIClient(XWalkUIClientInternal client) {
if (mContent == null) return;
checkThreadSafety();
* @param client the XWalkResourceClientInternal defined by callers.
* @since 1.0
*/
+ @XWalkAPI
public void setResourceClient(XWalkResourceClientInternal client) {
if (mContent == null) return;
checkThreadSafety();
void destroy() {
if (mContent == null) return;
- mExtensionManager.onDestroy();
+ ApplicationStatus.unregisterActivityStateListener(mActivityStateListener);
+ mActivityStateListener = null;
+ if (null != mExtensionManager) mExtensionManager.onDestroy();
mContent.destroy();
disableRemoteDebugging();
}
}
return super.dispatchKeyEvent(event);
}
+
+ private void onActivityStateChange(Activity activity, int newState) {
+ assert(getActivity() == activity);
+ switch (newState) {
+ case ActivityState.STARTED:
+ onShow();
+ break;
+ case ActivityState.PAUSED:
+ pauseTimers();
+ break;
+ case ActivityState.RESUMED:
+ resumeTimers();
+ break;
+ case ActivityState.DESTROYED:
+ onDestroy();
+ break;
+ case ActivityState.STOPPED:
+ onHide();
+ break;
+ default:
+ break;
+ }
+ }
}