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;
32 * <p>XWalkView represents an Android view for web apps/pages. Thus most of attributes
33 * for Android view are valid for this class. Since it internally uses
34 * <a href="http://developer.android.com/reference/android/view/SurfaceView.html">
35 * android.view.SurfaceView</a> for rendering web pages, it can't be resized, rotated,
36 * transformed and animated due to the limitations of SurfaceView. Besides, XWalkView won't
37 * be rendered if it's invisible.</p>
39 * <p>XWalkView needs hardware acceleration to render web pages. As a result, the
40 * AndroidManifest.xml of the caller's app must be appended with the attribute
41 * "android:hardwareAccelerated" and its value must be set as "true".</p>
43 * <application android:name="android.app.Application" android:label="XWalkUsers"
44 * android:hardwareAccelerated="true">
47 * <p>Crosswalk provides 2 major callback classes, namely {@link XWalkResourceClient} and
48 * {@link XWalkUIClient} for listening to the events related to resource loading and UI.
49 * By default, Crosswalk has a default implementation. Callers can override them if needed.</p>
51 * <p>Unlike other Android views, this class has to listen to system events like application life
52 * cycle, intents, and activity result. The web engine inside this view need to get and handle
53 * them. For example:</p>
56 * import android.app.Activity;
57 * import android.os.Bundle;
59 * import org.xwalk.core.XWalkResourceClient;
60 * import org.xwalk.core.XWalkUIClient;
61 * import org.xwalk.core.XWalkView;
63 * public class MyActivity extends Activity {
64 * XWalkView mXwalkView;
66 * class MyResourceClient extends XWalkResourceClient {
67 * MyResourceClient(XWalkView view) {
72 * WebResourceResponse shouldInterceptLoadRequest(XWalkView view, String url) {
78 * class MyUIClient extends XWalkUIClient {
79 * MyUIClient(XWalkView view) {
84 * void onFullscreenToggled(XWalkView view, String url) {
91 * protected void onCreate(Bundle savedInstanceState) {
92 * mXwalkView = new XWalkView(this, null);
93 * setContentView(mXwalkView);
94 * mXwalkView.setResourceClient(new MyResourceClient(mXwalkView));
95 * mXwalkView.setUIClient(new MyUIClient(mXwalkView));
96 * mXwalkView.load("http://www.crosswalk-project.org", null);
100 * protected void onPause() {
102 * if (mXwalkView != null) {
103 * mXwalkView.pauseTimers();
104 * mXwalkView.onHide();
109 * protected void onResume() {
111 * if (mXwalkView != null) {
112 * mXwalkView.resumeTimers();
113 * mXwalkView.onShow();
118 * protected void onDestroy() {
120 * if (mXwalkView != null) {
121 * mXwalkView.onDestroy();
126 * protected void onActivityResult(int requestCode, int resultCode, Intent data) {
127 * if (mXwalkView != null) {
128 * mXwalkView.onActivityResult(requestCode, resultCode, data);
133 * protected void onNewIntent(Intent intent) {
134 * if (mXwalkView != null) {
135 * mXwalkView.onNewIntent(intent);
141 public class XWalkView extends android.widget.FrameLayout {
143 static final String PLAYSTORE_DETAIL_URI = "market://details?id=";
145 private XWalkContent mContent;
146 private Activity mActivity;
147 private Context mContext;
148 private XWalkExtensionManager mExtensionManager;
149 private boolean mIsHidden;
151 /** Normal reload mode as default. */
152 public static final int RELOAD_NORMAL = 0;
153 /** Reload mode with bypassing the cache. */
154 public static final int RELOAD_IGNORE_CACHE = 1;
157 * Constructor for inflating via XML.
158 * @param context a Context object used to access application assets.
159 * @param attrs an AttributeSet passed to our parent.
161 public XWalkView(Context context, AttributeSet attrs) {
162 super(context, attrs);
166 init(context, attrs);
170 * Constructor for Crosswalk runtime. In shared mode, context isi
171 * different from activity. In embedded mode, they're same.
172 * @param context a Context object used to access application assets
173 * @param activity the activity for this XWalkView.
175 public XWalkView(Context context, Activity activity) {
176 super(context, null);
179 // Make sure mActivity is initialized before calling 'init' method.
180 mActivity = activity;
186 * Get the current activity passed from callers. It's never null.
187 * @return the activity instance passed from callers.
191 public Activity getActivity() {
192 if (mActivity != null) {
194 } else if (getContext() instanceof Activity) {
195 return (Activity)getContext();
198 // Never achieve here.
203 // TODO(yongsheng): we should remove this since we have getContext()?
207 public Context getViewContext() {
211 private void init(Context context, AttributeSet attrs) {
212 // Initialize chromium resources. Assign them the correct ids in
214 XWalkInternalResources.resetIds(context);
216 // Intialize library, paks and others.
218 XWalkViewDelegate.init(this);
219 } catch (UnsatisfiedLinkError e) {
220 final UnsatisfiedLinkError err = e;
221 final Activity activity = getActivity();
222 final String packageName = context.getPackageName();
223 String missingArch = XWalkViewDelegate.isRunningOnIA() ? "Intel" : "ARM";
224 final String message =
225 context.getString(R.string.cpu_arch_mismatch_message, missingArch);
227 AlertDialog.Builder builder = new AlertDialog.Builder(activity);
228 builder.setTitle(R.string.cpu_arch_mismatch_title)
230 .setOnCancelListener(new DialogInterface.OnCancelListener() {
232 public void onCancel(DialogInterface dialog) {
235 }).setPositiveButton(R.string.goto_store_button_label,
236 new DialogInterface.OnClickListener() {
238 public void onClick(DialogInterface dialog, int which) {
239 activity.startActivity(new Intent(Intent.ACTION_VIEW,
240 Uri.parse(PLAYSTORE_DETAIL_URI + packageName)));
243 }).setNeutralButton(R.string.report_feedback_button_label,
244 new DialogInterface.OnClickListener() {
246 public void onClick(DialogInterface dialog, int which) {
247 ApplicationErrorReport report = new ApplicationErrorReport();
248 report.type = ApplicationErrorReport.TYPE_CRASH;
249 report.packageName = report.processName = packageName;
251 ApplicationErrorReport.CrashInfo crash =
252 new ApplicationErrorReport.CrashInfo();
253 crash.exceptionClassName = err.getClass().getSimpleName();
254 crash.exceptionMessage = "CPU architecture mismatch";
255 StringWriter writer = new StringWriter();
256 PrintWriter print = new PrintWriter(writer);
257 err.printStackTrace(print);
258 crash.stackTrace = writer.toString();
259 StackTraceElement stack = err.getStackTrace()[0];
260 crash.throwClassName = stack.getClassName();
261 crash.throwFileName = stack.getFileName();
262 crash.throwLineNumber = stack.getLineNumber();
263 crash.throwMethodName = stack.getMethodName();
265 report.crashInfo = crash;
266 report.systemApp = false;
267 report.time = System.currentTimeMillis();
269 Intent intent = new Intent(Intent.ACTION_APP_ERROR);
270 intent.putExtra(Intent.EXTRA_BUG_REPORT, report);
271 activity.startActivity(intent);
275 builder.create().show();
279 initXWalkContent(context, attrs);
282 private void initXWalkContent(Context context, AttributeSet attrs) {
284 mContent = new XWalkContent(context, attrs, this);
286 new FrameLayout.LayoutParams(
287 FrameLayout.LayoutParams.MATCH_PARENT,
288 FrameLayout.LayoutParams.MATCH_PARENT));
291 // Set default XWalkClientImpl.
292 setXWalkClient(new XWalkClient(this));
293 // Set default XWalkWebChromeClient and DownloadListener. The default actions
294 // are provided via the following clients if special actions are not needed.
295 setXWalkWebChromeClient(new XWalkWebChromeClient(this));
297 // Set with internal implementation. Could be overwritten by embedders'
299 setUIClient(new XWalkUIClient(this));
300 setResourceClient(new XWalkResourceClient(this));
302 setDownloadListener(new XWalkDownloadListenerImpl(context));
303 setNavigationHandler(new XWalkNavigationHandlerImpl(context));
304 setNotificationService(new XWalkNotificationServiceImpl(context, this));
306 // Enable xwalk extension mechanism and start load extensions here.
307 // Note that it has to be after above initialization.
308 mExtensionManager = new XWalkExtensionManager(context, getActivity());
309 mExtensionManager.loadExtensions();
313 * Load a web page/app from a given base URL or a content.
314 * If url is null or empty and content is null or empty, then this function
316 * If content is not null, load the web page/app from the content.
317 * If content is not null and the url is not set, return "about:blank" ifi
318 * calling {@link XWalkView#getUrl()}.
319 * If content is null, try to load the content from the url.
321 * It supports URL schemes like 'http:', 'https:' and 'file:'.
322 * It can also load files from Android assets, e.g. 'file:///android_asset/'.
323 * @param url the url for web page/app.
324 * @param content the content for the web page/app. Could be empty.
326 public void load(String url, String content) {
327 if (mContent == null) return;
329 mContent.loadUrl(url, content);
333 * Load a web app from a given manifest.json file. If content is not null,
334 * load the manifest.json from the content. If content is null, try to load
335 * the manifest.json from the url. Note that url should not be null if the
336 * launched path defined in manifest.json is relative.
338 * It supports URL schemes like 'http:', 'https:' and 'file:'.
339 * It can also load files from Android assets, e.g. 'file:///android_asset/'.
340 * @param url the url for manifest.json.
341 * @param content the content for manifest.json.
343 public void loadAppFromManifest(String url, String content) {
344 if (mContent == null) return;
346 mContent.loadAppFromManifest(url, content);
350 * Reload a web app with a given mode.
351 * @param mode the reload mode.
353 public void reload(int mode) {
354 if (mContent == null) return;
356 mContent.reload(mode);
360 * Stop current loading progress.
362 public void stopLoading() {
363 if (mContent == null) return;
365 mContent.stopLoading();
369 * Get the url of current web page/app. This may be different from what's passed
371 * @return the url for current web page/app.
373 public String getUrl() {
374 if (mContent == null) return null;
376 return mContent.getUrl();
380 * Get the title of current web page/app. This may be different from what's passed
382 * @return the title for current web page/app.
384 public String getTitle() {
385 if (mContent == null) return null;
387 return mContent.getTitle();
391 * Get the original url specified by caller.
392 * @return the original url.
394 public String getOriginalUrl() {
395 if (mContent == null) return null;
397 return mContent.getOriginalUrl();
401 * Get the navigation history for current XWalkView. It's synchronized with
402 * this XWalkView if any backward/forward and navigation operations.
403 * @return the navigation history.
405 public XWalkNavigationHistory getNavigationHistory() {
406 if (mContent == null) return null;
408 return mContent.getNavigationHistory();
412 * Injects the supplied Java object into this XWalkView.
413 * Each method defined in the class of the object should be
414 * marked with {@link JavascriptInterface} if it's called by JavaScript.
415 * @param object the supplied Java object, called by JavaScript.
416 * @param name the name injected in JavaScript.
418 public void addJavascriptInterface(Object object, String name) {
419 if (mContent == null) return;
421 mContent.addJavascriptInterface(object, name);
425 * Evaluate a fragment of JavaScript code and get the result via callback.
426 * @param script the JavaScript string.
427 * @param callback the callback to handle the evaluated result.
429 public void evaluateJavascript(String script, ValueCallback<String> callback) {
430 if (mContent == null) return;
432 mContent.evaluateJavascript(script, callback);
436 * Clear the resource cache. Note that the cache is per-application, so this
437 * will clear the cache for all XWalkViews used.
438 * @param includeDiskFiles indicate whether to clear disk files for cache.
440 public void clearCache(boolean includeDiskFiles) {
441 if (mContent == null) return;
443 mContent.clearCache(includeDiskFiles);
447 * Indicate that a HTML element is occupying the whole screen.
448 * @return true if any HTML element is occupying the whole screen.
450 public boolean hasEnteredFullscreen() {
451 if (mContent == null) return false;
453 return mContent.hasEnteredFullscreen();
457 * Leave fullscreen mode if it's. Do nothing if it's not
460 public void leaveFullscreen() {
461 if (mContent == null) return;
463 mContent.exitFullscreen();
467 * Pause all layout, parsing and JavaScript timers for all XWalkView instances.
468 * Typically it should be called when the activity for this view is paused,
469 * and accordingly {@link #resumeTimers} should be called when the activity
472 * Note that it will globally impact all XWalkView instances, not limited to
473 * just this XWalkView.
475 public void pauseTimers() {
476 if (mContent == null) return;
478 mContent.pauseTimers();
482 * Resume all layout, parsing and JavaScript timers for all XWalkView instances.
483 * Typically it should be called when the activity for this view is resumed.
485 * Note that it will globally impact all XWalkView instances, not limited to
486 * just this XWalkView.
488 public void resumeTimers() {
489 if (mContent == null) return;
491 mContent.resumeTimers();
495 * Pause many other things except JavaScript timers inside rendering engine,
496 * like video player, modal dialogs, etc. See {@link #pauseTimers} about pausing
498 * Typically it should be called when the activity for this view is paused.
500 public void onHide() {
501 if (mContent == null || mIsHidden) return;
502 mExtensionManager.onPause();
508 * Resume video player, modal dialogs. Embedders are in charge of calling
509 * this during resuming this activity if they call onHide.
510 * Typically it should be called when the activity for this view is resumed.
512 public void onShow() {
513 if (mContent == null || !mIsHidden ) return;
514 mExtensionManager.onResume();
520 * Release internal resources occupied by this XWalkView.
522 public void onDestroy() {
527 * Pass through activity result to XWalkView. Many internal facilities need this
528 * to handle activity result like JavaScript dialog, Crosswalk extensions, etc.
529 * See <a href="http://developer.android.com/reference/android/app/Activity.html">
530 * android.app.Activity.onActivityResult()</a>.
531 * @param requestCode passed from android.app.Activity.onActivityResult().
532 * @param resultCode passed from android.app.Activity.onActivityResult().
533 * @param data passed from android.app.Activity.onActivityResult().
535 public void onActivityResult(int requestCode, int resultCode, Intent data) {
536 if (mContent == null) return;
537 mExtensionManager.onActivityResult(requestCode, resultCode, data);
538 mContent.onActivityResult(requestCode, resultCode, data);
542 * Pass through intents to XWalkView. Many internal facilities need this
543 * to receive the intents like web notification. See
544 * <a href="http://developer.android.com/reference/android/app/Activity.html">
545 * android.app.Activity.onNewIntent()</a>.
546 * @param intent passed from android.app.Activity.onNewIntent().
548 public boolean onNewIntent(Intent intent) {
549 if (mContent == null) return false;
550 return mContent.onNewIntent(intent);
554 * Save current internal state of this XWalkView. This can help restore this state
555 * afterwards restoring.
556 * @param outState the saved state for restoring.
558 public boolean saveState(Bundle outState) {
559 if (mContent == null) return false;
560 mContent.saveState(outState);
565 * Restore the state from the saved bundle data.
566 * @param inState the state saved from saveState().
567 * @return true if it can restore the state.
569 public boolean restoreState(Bundle inState) {
570 if (mContent == null) return false;
571 if (mContent.restoreState(inState) != null) return true;
576 * Get the API version of Crosswalk embedding API.
577 * @return the string of API level.
579 // TODO(yongsheng): make it static?
580 public String getAPIVersion() {
585 * Get the Crosswalk version.
586 * @return the string of Crosswalk.
588 // TODO(yongsheng): make it static?
589 public String getXWalkVersion() {
590 if (mContent == null) return null;
591 return mContent.getXWalkVersion();
595 * Embedders use this to customize their handlers to events/callbacks related
597 * @param client the XWalkUIClient defined by callers.
599 public void setUIClient(XWalkUIClient client) {
600 if (mContent == null) return;
602 mContent.setUIClient(client);
606 * Embedders use this to customize their handlers to events/callbacks related
607 * to resource loading.
608 * @param client the XWalkResourceClient defined by callers.
610 public void setResourceClient(XWalkResourceClient client) {
611 if (mContent == null) return;
613 mContent.setResourceClient(client);
617 * Inherit from <a href="http://developer.android.com/reference/android/view/View.html">
618 * android.view.View</a>. This class needs to handle some keys like
620 * @param keyCode passed from android.view.View.onKeyUp().
621 * @param event passed from android.view.View.onKeyUp().
624 public boolean onKeyUp(int keyCode, KeyEvent event) {
625 if (keyCode == KeyEvent.KEYCODE_BACK) {
626 // If there's navigation happens when app is fullscreen,
627 // the content will still be fullscreen after navigation.
628 // In such case, the back key will exit fullscreen first.
629 if (hasEnteredFullscreen()) {
632 } else if (canGoBack()) {
640 // TODO(yongsheng): this is not public.
644 public XWalkSettings getSettings() {
645 if (mContent == null) return null;
647 return mContent.getSettings();
651 * This method is used by Cordova for hacking.
652 * TODO(yongsheng): remove this and related test cases?
656 public void setNetworkAvailable(boolean networkUp) {
657 if (mContent == null) return;
659 mContent.setNetworkAvailable(networkUp);
663 * Enables remote debugging and returns the URL at which the dev tools server is listening
664 * for commands. The allowedUid argument can be used to specify the uid of the process that is
665 * permitted to connect.
666 * TODO(yongsheng): how to enable this in XWalkPreferences?
670 public String enableRemoteDebugging(int allowedUid) {
671 if (mContent == null) return null;
673 return mContent.enableRemoteDebugging(allowedUid);
677 * It's used for Presentation API.
680 public int getContentID() {
681 if (mContent == null) return -1;
682 return mContent.getRoutingID();
685 boolean canGoBack() {
686 if (mContent == null) return false;
688 return mContent.canGoBack();
692 if (mContent == null) return;
697 boolean canGoForward() {
698 if (mContent == null) return false;
700 return mContent.canGoForward();
704 if (mContent == null) return;
706 mContent.goForward();
709 void clearHistory() {
710 if (mContent == null) return;
712 mContent.clearHistory();
716 if (mContent == null) return;
717 mExtensionManager.onDestroy();
719 disableRemoteDebugging();
722 // Enables remote debugging and returns the URL at which the dev tools server is listening
723 // for commands. Only the current process is allowed to connect to the server.
724 String enableRemoteDebugging() {
725 return enableRemoteDebugging(mContext.getApplicationInfo().uid);
728 void disableRemoteDebugging() {
729 if (mContent == null) return;
731 mContent.disableRemoteDebugging();
734 private static void checkThreadSafety() {
735 if (Looper.myLooper() != Looper.getMainLooper()) {
736 Throwable throwable = new Throwable(
737 "Warning: A XWalkView method was called on thread '" +
738 Thread.currentThread().getName() + "'. " +
739 "All XWalkView methods must be called on the UI thread. ");
740 throw new RuntimeException(throwable);
744 boolean isOwnerActivityRunning() {
745 int status = ApplicationStatus.getStateForActivity(getActivity());
746 if (status == ActivityState.DESTROYED) return false;
750 void navigateTo(int offset) {
751 if (mContent == null) return;
752 mContent.navigateTo(offset);
755 void setOverlayVideoMode(boolean enabled) {
756 mContent.setOverlayVideoMode(enabled);
759 // Below methods are for test shell and instrumentation tests.
763 public void setXWalkClient(XWalkClient client) {
764 if (mContent == null) return;
766 mContent.setXWalkClient(client);
772 public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
773 if (mContent == null) return;
775 mContent.setXWalkWebChromeClient(client);
781 public void setDownloadListener(DownloadListener listener) {
782 if (mContent == null) return;
784 mContent.setDownloadListener(listener);
790 public void setNavigationHandler(XWalkNavigationHandler handler) {
791 if (mContent == null) return;
793 mContent.setNavigationHandler(handler);
799 public void setNotificationService(XWalkNotificationService service) {
800 if (mContent == null) return;
802 mContent.setNotificationService(service);