1 // Copyright (c) 2013-2014 Intel Corporation. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.xwalk.core;
7 import android.app.Activity;
8 import android.app.AlertDialog;
9 import android.app.ApplicationErrorReport;
10 import android.content.Context;
11 import android.content.DialogInterface;
12 import android.content.Intent;
13 import android.graphics.Rect;
14 import android.net.Uri;
15 import android.os.Bundle;
16 import android.os.Looper;
17 import android.util.AttributeSet;
18 import android.view.KeyEvent;
19 import android.view.ViewGroup;
20 import android.webkit.ValueCallback;
21 import android.widget.FrameLayout;
23 import java.io.PrintWriter;
24 import java.io.StringWriter;
26 import org.chromium.base.ActivityState;
27 import org.chromium.base.ApplicationStatus;
29 import org.xwalk.core.extension.XWalkExtensionManager;
30 import org.xwalk.core.extension.XWalkPathHelper;
33 * <p>XWalkView represents an Android view for web apps/pages. Thus most of attributes
34 * for Android view are valid for this class. Since it internally uses
35 * <a href="http://developer.android.com/reference/android/view/SurfaceView.html">
36 * android.view.SurfaceView</a> for rendering web pages by default, it can't be resized,
37 * rotated, transformed and animated due to the limitations of SurfaceView.
38 * Alternatively, if the preference key {@link XWalkPreferences#ANIMATABLE_XWALK_VIEW}
39 * is set to True, XWalkView can be transformed and animated because
40 * <a href="http://developer.android.com/reference/android/view/TextureView.html">
41 * TextureView</a> is intentionally used to render web pages for animation support.
42 * Besides, XWalkView won't be rendered if it's invisible.</p>
44 * <p>XWalkView needs hardware acceleration to render web pages. As a result, the
45 * AndroidManifest.xml of the caller's app must be appended with the attribute
46 * "android:hardwareAccelerated" and its value must be set as "true".</p>
48 * <application android:name="android.app.Application" android:label="XWalkUsers"
49 * android:hardwareAccelerated="true">
52 * <p>Crosswalk provides 2 major callback classes, namely {@link XWalkResourceClient} and
53 * {@link XWalkUIClient} for listening to the events related to resource loading and UI.
54 * By default, Crosswalk has a default implementation. Callers can override them if needed.</p>
56 * <p>Unlike other Android views, this class has to listen to system events like application life
57 * cycle, intents, and activity result. The web engine inside this view need to get and handle
58 * them. And the onDestroy() method of XWalkView MUST be called explicitly when an XWalkView
59 * won't be used anymore, otherwise it will cause the memory leak from the native side of the web
60 * engine. It's similar to the
61 * <a href="http://developer.android.com/reference/android/webkit/WebView.html#destroy()">
62 * destroy()</a> method of Android WebView. For example:</p>
65 * import android.app.Activity;
66 * import android.os.Bundle;
68 * import org.xwalk.core.XWalkResourceClient;
69 * import org.xwalk.core.XWalkUIClient;
70 * import org.xwalk.core.XWalkView;
72 * public class MyActivity extends Activity {
73 * XWalkView mXwalkView;
75 * class MyResourceClient extends XWalkResourceClient {
76 * MyResourceClient(XWalkView view) {
81 * WebResourceResponse shouldInterceptLoadRequest(XWalkView view, String url) {
87 * class MyUIClient extends XWalkUIClient {
88 * MyUIClient(XWalkView view) {
93 * void onFullscreenToggled(XWalkView view, String url) {
100 * protected void onCreate(Bundle savedInstanceState) {
101 * mXwalkView = new XWalkView(this, null);
102 * setContentView(mXwalkView);
103 * mXwalkView.setResourceClient(new MyResourceClient(mXwalkView));
104 * mXwalkView.setUIClient(new MyUIClient(mXwalkView));
105 * mXwalkView.load("http://www.crosswalk-project.org", null);
109 * protected void onPause() {
111 * if (mXwalkView != null) {
112 * mXwalkView.pauseTimers();
113 * mXwalkView.onHide();
118 * protected void onResume() {
120 * if (mXwalkView != null) {
121 * mXwalkView.resumeTimers();
122 * mXwalkView.onShow();
127 * protected void onDestroy() {
129 * if (mXwalkView != null) {
130 * mXwalkView.onDestroy();
135 * protected void onActivityResult(int requestCode, int resultCode, Intent data) {
136 * if (mXwalkView != null) {
137 * mXwalkView.onActivityResult(requestCode, resultCode, data);
142 * protected void onNewIntent(Intent intent) {
143 * if (mXwalkView != null) {
144 * mXwalkView.onNewIntent(intent);
150 public class XWalkView extends android.widget.FrameLayout {
152 static final String PLAYSTORE_DETAIL_URI = "market://details?id=";
154 private XWalkContent mContent;
155 private Activity mActivity;
156 private Context mContext;
157 private XWalkExtensionManager mExtensionManager;
158 private boolean mIsHidden;
160 /** Normal reload mode as default. */
161 public static final int RELOAD_NORMAL = 0;
162 /** Reload mode with bypassing the cache. */
163 public static final int RELOAD_IGNORE_CACHE = 1;
166 * Constructor for inflating via XML.
167 * @param context a Context object used to access application assets.
168 * @param attrs an AttributeSet passed to our parent.
170 public XWalkView(Context context, AttributeSet attrs) {
171 super(context, attrs);
175 init(context, attrs);
179 * Constructor for Crosswalk runtime. In shared mode, context isi
180 * different from activity. In embedded mode, they're same.
181 * @param context a Context object used to access application assets
182 * @param activity the activity for this XWalkView.
184 public XWalkView(Context context, Activity activity) {
185 super(context, null);
188 // Make sure mActivity is initialized before calling 'init' method.
189 mActivity = activity;
195 * Get the current activity passed from callers. It's never null.
196 * @return the activity instance passed from callers.
200 public Activity getActivity() {
201 if (mActivity != null) {
203 } else if (getContext() instanceof Activity) {
204 return (Activity)getContext();
207 // Never achieve here.
212 // TODO(yongsheng): we should remove this since we have getContext()?
216 public Context getViewContext() {
220 private void init(Context context, AttributeSet attrs) {
221 // Initialize chromium resources. Assign them the correct ids in
223 XWalkInternalResources.resetIds(context);
225 // Intialize library, paks and others.
227 XWalkViewDelegate.init(this);
228 } catch (UnsatisfiedLinkError e) {
229 final UnsatisfiedLinkError err = e;
230 final Activity activity = getActivity();
231 final String packageName = context.getPackageName();
232 String missingArch = XWalkViewDelegate.isRunningOnIA() ? "Intel" : "ARM";
233 final String message =
234 context.getString(R.string.cpu_arch_mismatch_message, missingArch);
236 AlertDialog.Builder builder = new AlertDialog.Builder(activity);
237 builder.setTitle(R.string.cpu_arch_mismatch_title)
239 .setOnCancelListener(new DialogInterface.OnCancelListener() {
241 public void onCancel(DialogInterface dialog) {
244 }).setPositiveButton(R.string.goto_store_button_label,
245 new DialogInterface.OnClickListener() {
247 public void onClick(DialogInterface dialog, int which) {
248 activity.startActivity(new Intent(Intent.ACTION_VIEW,
249 Uri.parse(PLAYSTORE_DETAIL_URI + packageName)));
252 }).setNeutralButton(R.string.report_feedback_button_label,
253 new DialogInterface.OnClickListener() {
255 public void onClick(DialogInterface dialog, int which) {
256 ApplicationErrorReport report = new ApplicationErrorReport();
257 report.type = ApplicationErrorReport.TYPE_CRASH;
258 report.packageName = report.processName = packageName;
260 ApplicationErrorReport.CrashInfo crash =
261 new ApplicationErrorReport.CrashInfo();
262 crash.exceptionClassName = err.getClass().getSimpleName();
263 crash.exceptionMessage = "CPU architecture mismatch";
264 StringWriter writer = new StringWriter();
265 PrintWriter print = new PrintWriter(writer);
266 err.printStackTrace(print);
267 crash.stackTrace = writer.toString();
268 StackTraceElement stack = err.getStackTrace()[0];
269 crash.throwClassName = stack.getClassName();
270 crash.throwFileName = stack.getFileName();
271 crash.throwLineNumber = stack.getLineNumber();
272 crash.throwMethodName = stack.getMethodName();
274 report.crashInfo = crash;
275 report.systemApp = false;
276 report.time = System.currentTimeMillis();
278 Intent intent = new Intent(Intent.ACTION_APP_ERROR);
279 intent.putExtra(Intent.EXTRA_BUG_REPORT, report);
280 activity.startActivity(intent);
284 builder.create().show();
288 initXWalkContent(context, attrs);
291 private void initXWalkContent(Context context, AttributeSet attrs) {
293 mContent = new XWalkContent(context, attrs, this);
295 new FrameLayout.LayoutParams(
296 FrameLayout.LayoutParams.MATCH_PARENT,
297 FrameLayout.LayoutParams.MATCH_PARENT));
300 // Set default XWalkClientImpl.
301 setXWalkClient(new XWalkClient(this));
302 // Set default XWalkWebChromeClient and DownloadListener. The default actions
303 // are provided via the following clients if special actions are not needed.
304 setXWalkWebChromeClient(new XWalkWebChromeClient(this));
306 // Set with internal implementation. Could be overwritten by embedders'
308 setUIClient(new XWalkUIClient(this));
309 setResourceClient(new XWalkResourceClient(this));
311 setDownloadListener(new XWalkDownloadListenerImpl(context));
312 setNavigationHandler(new XWalkNavigationHandlerImpl(context));
313 setNotificationService(new XWalkNotificationServiceImpl(context, this));
315 // Enable xwalk extension mechanism and start load extensions here.
316 // Note that it has to be after above initialization.
317 mExtensionManager = new XWalkExtensionManager(context, getActivity());
318 mExtensionManager.loadExtensions();
320 XWalkPathHelper.initialize();
321 XWalkPathHelper.setCacheDirectory(
322 mContext.getApplicationContext().getCacheDir().getPath());
323 XWalkPathHelper.setExternalCacheDirectory(
324 mContext.getApplicationContext().getExternalCacheDir().getPath());
328 * Load a web page/app from a given base URL or a content.
329 * If url is null or empty and content is null or empty, then this function
331 * If content is not null, load the web page/app from the content.
332 * If content is not null and the url is not set, return "about:blank" ifi
333 * calling {@link XWalkView#getUrl()}.
334 * If content is null, try to load the content from the url.
336 * It supports URL schemes like 'http:', 'https:' and 'file:'.
337 * It can also load files from Android assets, e.g. 'file:///android_asset/'.
338 * @param url the url for web page/app.
339 * @param content the content for the web page/app. Could be empty.
341 public void load(String url, String content) {
342 if (mContent == null) return;
344 mContent.loadUrl(url, content);
348 * Load a web app from a given manifest.json file. If content is not null,
349 * load the manifest.json from the content. If content is null, try to load
350 * the manifest.json from the url. Note that url should not be null if the
351 * launched path defined in manifest.json is relative.
353 * It supports URL schemes like 'http:', 'https:' and 'file:'.
354 * It can also load files from Android assets, e.g. 'file:///android_asset/'.
355 * @param url the url for manifest.json.
356 * @param content the content for manifest.json.
358 public void loadAppFromManifest(String url, String content) {
359 if (mContent == null) return;
361 mContent.loadAppFromManifest(url, content);
365 * Reload a web app with a given mode.
366 * @param mode the reload mode.
368 public void reload(int mode) {
369 if (mContent == null) return;
371 mContent.reload(mode);
375 * Stop current loading progress.
377 public void stopLoading() {
378 if (mContent == null) return;
380 mContent.stopLoading();
384 * Get the url of current web page/app. This may be different from what's passed
386 * @return the url for current web page/app.
388 public String getUrl() {
389 if (mContent == null) return null;
391 return mContent.getUrl();
395 * Get the title of current web page/app. This may be different from what's passed
397 * @return the title for current web page/app.
399 public String getTitle() {
400 if (mContent == null) return null;
402 return mContent.getTitle();
406 * Get the original url specified by caller.
407 * @return the original url.
409 public String getOriginalUrl() {
410 if (mContent == null) return null;
412 return mContent.getOriginalUrl();
416 * Get the navigation history for current XWalkView. It's synchronized with
417 * this XWalkView if any backward/forward and navigation operations.
418 * @return the navigation history.
420 public XWalkNavigationHistory getNavigationHistory() {
421 if (mContent == null) return null;
423 return mContent.getNavigationHistory();
427 * Injects the supplied Java object into this XWalkView.
428 * Each method defined in the class of the object should be
429 * marked with {@link JavascriptInterface} if it's called by JavaScript.
430 * @param object the supplied Java object, called by JavaScript.
431 * @param name the name injected in JavaScript.
433 public void addJavascriptInterface(Object object, String name) {
434 if (mContent == null) return;
436 mContent.addJavascriptInterface(object, name);
440 * Evaluate a fragment of JavaScript code and get the result via callback.
441 * @param script the JavaScript string.
442 * @param callback the callback to handle the evaluated result.
444 public void evaluateJavascript(String script, ValueCallback<String> callback) {
445 if (mContent == null) return;
447 mContent.evaluateJavascript(script, callback);
451 * Clear the resource cache. Note that the cache is per-application, so this
452 * will clear the cache for all XWalkViews used.
453 * @param includeDiskFiles indicate whether to clear disk files for cache.
455 public void clearCache(boolean includeDiskFiles) {
456 if (mContent == null) return;
458 mContent.clearCache(includeDiskFiles);
462 * Indicate that a HTML element is occupying the whole screen.
463 * @return true if any HTML element is occupying the whole screen.
465 public boolean hasEnteredFullscreen() {
466 if (mContent == null) return false;
468 return mContent.hasEnteredFullscreen();
472 * Leave fullscreen mode if it's. Do nothing if it's not
475 public void leaveFullscreen() {
476 if (mContent == null) return;
478 mContent.exitFullscreen();
482 * Pause all layout, parsing and JavaScript timers for all XWalkView instances.
483 * Typically it should be called when the activity for this view is paused,
484 * and accordingly {@link #resumeTimers} should be called when the activity
487 * Note that it will globally impact all XWalkView instances, not limited to
488 * just this XWalkView.
490 public void pauseTimers() {
491 if (mContent == null) return;
493 mContent.pauseTimers();
497 * Resume all layout, parsing and JavaScript timers for all XWalkView instances.
498 * Typically it should be called when the activity for this view is resumed.
500 * Note that it will globally impact all XWalkView instances, not limited to
501 * just this XWalkView.
503 public void resumeTimers() {
504 if (mContent == null) return;
506 mContent.resumeTimers();
510 * Pause many other things except JavaScript timers inside rendering engine,
511 * like video player, modal dialogs, etc. See {@link #pauseTimers} about pausing
513 * Typically it should be called when the activity for this view is paused.
515 public void onHide() {
516 if (mContent == null || mIsHidden) return;
517 mExtensionManager.onPause();
523 * Resume video player, modal dialogs. Embedders are in charge of calling
524 * this during resuming this activity if they call onHide.
525 * Typically it should be called when the activity for this view is resumed.
527 public void onShow() {
528 if (mContent == null || !mIsHidden ) return;
529 mExtensionManager.onResume();
535 * Release internal resources occupied by this XWalkView.
537 public void onDestroy() {
542 * Pass through activity result to XWalkView. Many internal facilities need this
543 * to handle activity result like JavaScript dialog, Crosswalk extensions, etc.
544 * See <a href="http://developer.android.com/reference/android/app/Activity.html">
545 * android.app.Activity.onActivityResult()</a>.
546 * @param requestCode passed from android.app.Activity.onActivityResult().
547 * @param resultCode passed from android.app.Activity.onActivityResult().
548 * @param data passed from android.app.Activity.onActivityResult().
550 public void onActivityResult(int requestCode, int resultCode, Intent data) {
551 if (mContent == null) return;
552 mExtensionManager.onActivityResult(requestCode, resultCode, data);
553 mContent.onActivityResult(requestCode, resultCode, data);
557 * Pass through intents to XWalkView. Many internal facilities need this
558 * to receive the intents like web notification. See
559 * <a href="http://developer.android.com/reference/android/app/Activity.html">
560 * android.app.Activity.onNewIntent()</a>.
561 * @param intent passed from android.app.Activity.onNewIntent().
563 public boolean onNewIntent(Intent intent) {
564 if (mContent == null) return false;
565 return mContent.onNewIntent(intent);
569 * Save current internal state of this XWalkView. This can help restore this state
570 * afterwards restoring.
571 * @param outState the saved state for restoring.
573 public boolean saveState(Bundle outState) {
574 if (mContent == null) return false;
575 mContent.saveState(outState);
580 * Restore the state from the saved bundle data.
581 * @param inState the state saved from saveState().
582 * @return true if it can restore the state.
584 public boolean restoreState(Bundle inState) {
585 if (mContent == null) return false;
586 if (mContent.restoreState(inState) != null) return true;
591 * Get the API version of Crosswalk embedding API.
592 * @return the string of API level.
594 // TODO(yongsheng): make it static?
595 public String getAPIVersion() {
600 * Get the Crosswalk version.
601 * @return the string of Crosswalk.
603 // TODO(yongsheng): make it static?
604 public String getXWalkVersion() {
605 if (mContent == null) return null;
606 return mContent.getXWalkVersion();
610 * Embedders use this to customize their handlers to events/callbacks related
612 * @param client the XWalkUIClient defined by callers.
614 public void setUIClient(XWalkUIClient client) {
615 if (mContent == null) return;
617 mContent.setUIClient(client);
621 * Embedders use this to customize their handlers to events/callbacks related
622 * to resource loading.
623 * @param client the XWalkResourceClient defined by callers.
625 public void setResourceClient(XWalkResourceClient client) {
626 if (mContent == null) return;
628 mContent.setResourceClient(client);
632 * Inherit from <a href="http://developer.android.com/reference/android/view/View.html">
633 * android.view.View</a>. This class needs to handle some keys like
635 * @param keyCode passed from android.view.View.onKeyUp().
636 * @param event passed from android.view.View.onKeyUp().
639 public boolean onKeyUp(int keyCode, KeyEvent event) {
640 if (keyCode == KeyEvent.KEYCODE_BACK) {
641 // If there's navigation happens when app is fullscreen,
642 // the content will still be fullscreen after navigation.
643 // In such case, the back key will exit fullscreen first.
644 if (hasEnteredFullscreen()) {
647 } else if (canGoBack()) {
655 // TODO(yongsheng): this is not public.
659 public XWalkSettings getSettings() {
660 if (mContent == null) return null;
662 return mContent.getSettings();
666 * This method is used by Cordova for hacking.
667 * TODO(yongsheng): remove this and related test cases?
671 public void setNetworkAvailable(boolean networkUp) {
672 if (mContent == null) return;
674 mContent.setNetworkAvailable(networkUp);
678 * Enables remote debugging and returns the URL at which the dev tools server is listening
679 * for commands. The allowedUid argument can be used to specify the uid of the process that is
680 * permitted to connect.
681 * TODO(yongsheng): how to enable this in XWalkPreferences?
685 public String enableRemoteDebugging(int allowedUid) {
686 if (mContent == null) return null;
688 return mContent.enableRemoteDebugging(allowedUid);
692 * It's used for Presentation API.
695 public int getContentID() {
696 if (mContent == null) return -1;
697 return mContent.getRoutingID();
700 boolean canGoBack() {
701 if (mContent == null) return false;
703 return mContent.canGoBack();
707 if (mContent == null) return;
712 boolean canGoForward() {
713 if (mContent == null) return false;
715 return mContent.canGoForward();
719 if (mContent == null) return;
721 mContent.goForward();
724 void clearHistory() {
725 if (mContent == null) return;
727 mContent.clearHistory();
731 if (mContent == null) return;
732 mExtensionManager.onDestroy();
734 disableRemoteDebugging();
737 // Enables remote debugging and returns the URL at which the dev tools server is listening
738 // for commands. Only the current process is allowed to connect to the server.
739 String enableRemoteDebugging() {
740 return enableRemoteDebugging(mContext.getApplicationInfo().uid);
743 void disableRemoteDebugging() {
744 if (mContent == null) return;
746 mContent.disableRemoteDebugging();
749 private static void checkThreadSafety() {
750 if (Looper.myLooper() != Looper.getMainLooper()) {
751 Throwable throwable = new Throwable(
752 "Warning: A XWalkView method was called on thread '" +
753 Thread.currentThread().getName() + "'. " +
754 "All XWalkView methods must be called on the UI thread. ");
755 throw new RuntimeException(throwable);
759 boolean isOwnerActivityRunning() {
760 int status = ApplicationStatus.getStateForActivity(getActivity());
761 if (status == ActivityState.DESTROYED) return false;
765 void navigateTo(int offset) {
766 if (mContent == null) return;
767 mContent.navigateTo(offset);
770 void setOverlayVideoMode(boolean enabled) {
771 mContent.setOverlayVideoMode(enabled);
774 // Below methods are for test shell and instrumentation tests.
778 public void setXWalkClient(XWalkClient client) {
779 if (mContent == null) return;
781 mContent.setXWalkClient(client);
787 public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
788 if (mContent == null) return;
790 mContent.setXWalkWebChromeClient(client);
796 public void setDownloadListener(DownloadListener listener) {
797 if (mContent == null) return;
799 mContent.setDownloadListener(listener);
805 public void setNavigationHandler(XWalkNavigationHandler handler) {
806 if (mContent == null) return;
808 mContent.setNavigationHandler(handler);
814 public void setNotificationService(XWalkNotificationService service) {
815 if (mContent == null) return;
817 mContent.setNotificationService(service);