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.internal;
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.Environment;
17 import android.os.Looper;
18 import android.util.AttributeSet;
19 import android.view.KeyEvent;
20 import android.view.ViewGroup;
21 import android.webkit.ValueCallback;
22 import android.widget.FrameLayout;
25 import java.io.PrintWriter;
26 import java.io.StringWriter;
27 import java.lang.ref.WeakReference;
29 import org.chromium.base.ActivityState;
30 import org.chromium.base.ApplicationStatus;
31 import org.chromium.base.ApplicationStatus.ActivityStateListener;
33 import org.xwalk.core.internal.extension.XWalkExtensionManager;
34 import org.xwalk.core.internal.extension.XWalkPathHelper;
37 * <p>XWalkViewInternal represents an Android view for web apps/pages. Thus most of attributes
38 * for Android view are valid for this class. Since it internally uses
39 * <a href="http://developer.android.com/reference/android/view/SurfaceView.html">
40 * android.view.SurfaceView</a> for rendering web pages by default, it can't be resized,
41 * rotated, transformed and animated due to the limitations of SurfaceView.
42 * Alternatively, if the preference key {@link XWalkPreferencesInternal#ANIMATABLE_XWALK_VIEW}
43 * is set to True, XWalkViewInternal can be transformed and animated because
44 * <a href="http://developer.android.com/reference/android/view/TextureView.html">
45 * TextureView</a> is intentionally used to render web pages for animation support.
46 * Besides, XWalkViewInternal won't be rendered if it's invisible.</p>
48 * <p>XWalkViewInternal needs hardware acceleration to render web pages. As a result, the
49 * AndroidManifest.xml of the caller's app must be appended with the attribute
50 * "android:hardwareAccelerated" and its value must be set as "true".</p>
52 * <application android:name="android.app.Application" android:label="XWalkUsers"
53 * android:hardwareAccelerated="true">
56 * <p>Crosswalk provides 2 major callback classes, namely {@link XWalkResourceClientInternal} and
57 * {@link XWalkUIClientInternal} for listening to the events related to resource loading and UI.
58 * By default, Crosswalk has a default implementation. Callers can override them if needed.</p>
60 * <p>Unlike other Android views, this class has to listen to system events like intents and activity result.
61 * The web engine inside this view need to get and handle them.
62 * With contianer activity's lifecycle change, XWalkViewInternal will pause all timers and other
63 * components like videos when activity paused, resume back them when activity resumed.
64 * When activity is about to destroy, XWalkViewInternal will destroy itself as well.
65 * Embedders can also call onHide() and pauseTimers() to explicitly pause XWalkViewInternal.
66 * Similarily with onShow(), resumeTimers() and onDestroy().
71 * import android.app.Activity;
72 * import android.os.Bundle;
74 * import org.xwalk.core.internal.XWalkResourceClientInternal;
75 * import org.xwalk.core.internal.XWalkUIClientInternal;
76 * import org.xwalk.core.internal.XWalkViewInternal;
78 * public class MyActivity extends Activity {
79 * XWalkViewInternal mXwalkView;
81 * class MyResourceClient extends XWalkResourceClientInternal {
82 * MyResourceClient(XWalkViewInternal view) {
87 * WebResourceResponse shouldInterceptLoadRequest(XWalkViewInternal view, String url) {
93 * class MyUIClient extends XWalkUIClientInternal {
94 * MyUIClient(XWalkViewInternal view) {
99 * void onFullscreenToggled(XWalkViewInternal view, String url) {
106 * protected void onCreate(Bundle savedInstanceState) {
107 * mXwalkView = new XWalkViewInternal(this, null);
108 * setContentView(mXwalkView);
109 * mXwalkView.setResourceClient(new MyResourceClient(mXwalkView));
110 * mXwalkView.setUIClient(new MyUIClient(mXwalkView));
111 * mXwalkView.load("http://www.crosswalk-project.org", null);
115 * protected void onActivityResult(int requestCode, int resultCode, Intent data) {
116 * if (mXwalkView != null) {
117 * mXwalkView.onActivityResult(requestCode, resultCode, data);
122 * protected void onNewIntent(Intent intent) {
123 * if (mXwalkView != null) {
124 * mXwalkView.onNewIntent(intent);
130 public class XWalkViewInternal extends android.widget.FrameLayout {
132 private class XWalkActivityStateListener implements ActivityStateListener {
133 WeakReference<XWalkViewInternal> mXWalkViewRef;
135 XWalkActivityStateListener(XWalkViewInternal view) {
136 mXWalkViewRef = new WeakReference<XWalkViewInternal>(view);
140 public void onActivityStateChange(Activity activity, int newState) {
141 XWalkViewInternal view = mXWalkViewRef.get();
142 if (view == null) return;
143 view.onActivityStateChange(activity, newState);
147 static final String PLAYSTORE_DETAIL_URI = "market://details?id=";
149 private XWalkContent mContent;
150 private Activity mActivity;
151 private Context mContext;
152 private XWalkExtensionManager mExtensionManager;
153 private boolean mIsHidden;
154 private XWalkActivityStateListener mActivityStateListener;
157 * Normal reload mode as default.
160 public static final int RELOAD_NORMAL = 0;
162 * Reload mode with bypassing the cache.
165 public static final int RELOAD_IGNORE_CACHE = 1;
168 * Constructor for inflating via XML.
169 * @param context a Context object used to access application assets.
170 * @param attrs an AttributeSet passed to our parent.
173 public XWalkViewInternal(Context context, AttributeSet attrs) {
174 super(context, attrs);
178 init(context, attrs);
182 * Constructor for Crosswalk runtime. In shared mode, context isi
183 * different from activity. In embedded mode, they're same.
184 * @param context a Context object used to access application assets
185 * @param activity the activity for this XWalkViewInternal.
188 public XWalkViewInternal(Context context, Activity activity) {
189 super(context, null);
192 // Make sure mActivity is initialized before calling 'init' method.
193 mActivity = activity;
199 * Get the current activity passed from callers. It's never null.
200 * @return the activity instance passed from callers.
204 public Activity getActivity() {
205 if (mActivity != null) {
207 } else if (getContext() instanceof Activity) {
208 return (Activity)getContext();
211 // Never achieve here.
216 // TODO(yongsheng): we should remove this since we have getContext()?
220 public Context getViewContext() {
224 private void init(Context context, AttributeSet attrs) {
225 // Initialize chromium resources. Assign them the correct ids in
227 XWalkInternalResources.resetIds(context);
229 // Intialize library, paks and others.
231 XWalkViewDelegate.init(this);
232 mActivityStateListener = new XWalkActivityStateListener(this);
233 ApplicationStatus.registerStateListenerForActivity(
234 mActivityStateListener, getActivity());
235 } catch (Throwable e) {
236 // Try to find if there is UnsatisfiedLinkError in the cause chain of the met Throwable.
237 Throwable linkError = e;
239 if (linkError == null) throw new RuntimeException(e);
240 if (linkError instanceof UnsatisfiedLinkError) break;
241 if (linkError.getCause() == null ||
242 linkError.getCause().equals(linkError)) {
243 throw new RuntimeException(e);
245 linkError = linkError.getCause();
247 final UnsatisfiedLinkError err = (UnsatisfiedLinkError) linkError;
248 final Activity activity = getActivity();
249 final String packageName = context.getPackageName();
250 String missingArch = XWalkViewDelegate.isRunningOnIA() ? "Intel" : "ARM";
251 final String message =
252 context.getString(R.string.cpu_arch_mismatch_message, missingArch);
254 AlertDialog.Builder builder = new AlertDialog.Builder(activity);
255 builder.setTitle(R.string.cpu_arch_mismatch_title)
257 .setOnCancelListener(new DialogInterface.OnCancelListener() {
259 public void onCancel(DialogInterface dialog) {
262 }).setPositiveButton(R.string.goto_store_button_label,
263 new DialogInterface.OnClickListener() {
265 public void onClick(DialogInterface dialog, int which) {
266 activity.startActivity(new Intent(Intent.ACTION_VIEW,
267 Uri.parse(PLAYSTORE_DETAIL_URI + packageName)));
270 }).setNeutralButton(R.string.report_feedback_button_label,
271 new DialogInterface.OnClickListener() {
273 public void onClick(DialogInterface dialog, int which) {
274 ApplicationErrorReport report = new ApplicationErrorReport();
275 report.type = ApplicationErrorReport.TYPE_CRASH;
276 report.packageName = report.processName = packageName;
278 ApplicationErrorReport.CrashInfo crash =
279 new ApplicationErrorReport.CrashInfo();
280 crash.exceptionClassName = err.getClass().getSimpleName();
281 crash.exceptionMessage = "CPU architecture mismatch";
282 StringWriter writer = new StringWriter();
283 PrintWriter print = new PrintWriter(writer);
284 err.printStackTrace(print);
285 crash.stackTrace = writer.toString();
286 StackTraceElement stack = err.getStackTrace()[0];
287 crash.throwClassName = stack.getClassName();
288 crash.throwFileName = stack.getFileName();
289 crash.throwLineNumber = stack.getLineNumber();
290 crash.throwMethodName = stack.getMethodName();
292 report.crashInfo = crash;
293 report.systemApp = false;
294 report.time = System.currentTimeMillis();
296 Intent intent = new Intent(Intent.ACTION_APP_ERROR);
297 intent.putExtra(Intent.EXTRA_BUG_REPORT, report);
298 activity.startActivity(intent);
302 builder.create().show();
306 initXWalkContent(context, attrs);
309 private void initXWalkContent(Context context, AttributeSet attrs) {
311 mContent = new XWalkContent(context, attrs, this);
313 new FrameLayout.LayoutParams(
314 FrameLayout.LayoutParams.MATCH_PARENT,
315 FrameLayout.LayoutParams.MATCH_PARENT));
318 // Set default XWalkClientImpl.
319 setXWalkClient(new XWalkClient(this));
320 // Set default XWalkWebChromeClient and DownloadListener. The default actions
321 // are provided via the following clients if special actions are not needed.
322 setXWalkWebChromeClient(new XWalkWebChromeClient(this));
324 // Set with internal implementation. Could be overwritten by embedders'
326 setUIClient(new XWalkUIClientInternal(this));
327 setResourceClient(new XWalkResourceClientInternal(this));
329 setDownloadListener(new XWalkDownloadListenerImpl(context));
330 setNavigationHandler(new XWalkNavigationHandlerImpl(context));
331 setNotificationService(new XWalkNotificationServiceImpl(context, this));
333 // Enable xwalk extension mechanism and start load extensions here.
334 // Note that it has to be after above initialization.
335 mExtensionManager = new XWalkExtensionManager(context, getActivity());
336 mExtensionManager.loadExtensions();
338 XWalkPathHelper.initialize();
339 XWalkPathHelper.setCacheDirectory(
340 mContext.getApplicationContext().getCacheDir().getPath());
342 String state = Environment.getExternalStorageState();
343 if (Environment.MEDIA_MOUNTED.equals(state) ||
344 Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
345 File extCacheDir = mContext.getApplicationContext().getExternalCacheDir();
346 if (null != extCacheDir) {
347 XWalkPathHelper.setExternalCacheDirectory(extCacheDir.getPath());
353 * Load a web page/app from a given base URL or a content.
354 * If url is null or empty and content is null or empty, then this function
356 * If content is not null, load the web page/app from the content.
357 * If content is not null and the url is not set, return "about:blank" ifi
358 * calling {@link XWalkViewInternal#getUrl()}.
359 * If content is null, try to load the content from the url.
361 * It supports URL schemes like 'http:', 'https:' and 'file:'.
362 * It can also load files from Android assets, e.g. 'file:///android_asset/'.
363 * @param url the url for web page/app.
364 * @param content the content for the web page/app. Could be empty.
367 public void load(String url, String content) {
368 if (mContent == null) return;
370 mContent.loadUrl(url, content);
374 * Load a web app from a given manifest.json file. If content is not null,
375 * load the manifest.json from the content. If content is null, try to load
376 * the manifest.json from the url. Note that url should not be null if the
377 * launched path defined in manifest.json is relative.
379 * It supports URL schemes like 'http:', 'https:' and 'file:'.
380 * It can also load files from Android assets, e.g. 'file:///android_asset/'.
381 * @param url the url for manifest.json.
382 * @param content the content for manifest.json.
385 public void loadAppFromManifest(String url, String content) {
386 if (mContent == null) return;
388 mContent.loadAppFromManifest(url, content);
392 * Reload a web app with a given mode.
393 * @param mode the reload mode.
396 public void reload(int mode) {
397 if (mContent == null) return;
399 mContent.reload(mode);
403 * Stop current loading progress.
406 public void stopLoading() {
407 if (mContent == null) return;
409 mContent.stopLoading();
413 * Get the url of current web page/app. This may be different from what's passed
415 * @return the url for current web page/app.
418 public String getUrl() {
419 if (mContent == null) return null;
421 return mContent.getUrl();
425 * Get the title of current web page/app. This may be different from what's passed
427 * @return the title for current web page/app.
430 public String getTitle() {
431 if (mContent == null) return null;
433 return mContent.getTitle();
437 * Get the original url specified by caller.
438 * @return the original url.
441 public String getOriginalUrl() {
442 if (mContent == null) return null;
444 return mContent.getOriginalUrl();
448 * Get the navigation history for current XWalkViewInternal. It's synchronized with
449 * this XWalkViewInternal if any backward/forward and navigation operations.
450 * @return the navigation history.
453 public XWalkNavigationHistoryInternal getNavigationHistory() {
454 if (mContent == null) return null;
456 return mContent.getNavigationHistory();
460 * Injects the supplied Java object into this XWalkViewInternal.
461 * Each method defined in the class of the object should be
462 * marked with {@link JavascriptInterface} if it's called by JavaScript.
463 * @param object the supplied Java object, called by JavaScript.
464 * @param name the name injected in JavaScript.
467 public void addJavascriptInterface(Object object, String name) {
468 if (mContent == null) return;
470 mContent.addJavascriptInterface(object, name);
474 * Evaluate a fragment of JavaScript code and get the result via callback.
475 * @param script the JavaScript string.
476 * @param callback the callback to handle the evaluated result.
479 public void evaluateJavascript(String script, ValueCallback<String> callback) {
480 if (mContent == null) return;
482 mContent.evaluateJavascript(script, callback);
486 * Clear the resource cache. Note that the cache is per-application, so this
487 * will clear the cache for all XWalkViews used.
488 * @param includeDiskFiles indicate whether to clear disk files for cache.
491 public void clearCache(boolean includeDiskFiles) {
492 if (mContent == null) return;
494 mContent.clearCache(includeDiskFiles);
498 * Indicate that a HTML element is occupying the whole screen.
499 * @return true if any HTML element is occupying the whole screen.
502 public boolean hasEnteredFullscreen() {
503 if (mContent == null) return false;
505 return mContent.hasEnteredFullscreen();
509 * Leave fullscreen mode if it's. Do nothing if it's not
513 public void leaveFullscreen() {
514 if (mContent == null) return;
516 mContent.exitFullscreen();
520 * Pause all layout, parsing and JavaScript timers for all XWalkViewInternal instances.
521 * It will be called when the container Activity get paused. It can also be explicitly
522 * called to pause timers.
524 * Note that it will globally impact all XWalkViewInternal instances, not limited to
525 * just this XWalkViewInternal.
529 public void pauseTimers() {
530 if (mContent == null) return;
532 mContent.pauseTimers();
536 * Resume all layout, parsing and JavaScript timers for all XWalkViewInternal instances.
537 * It will be called when the container Activity get resumed. It can also be explicitly
538 * called to resume timers.
540 * Note that it will globally impact all XWalkViewInternal instances, not limited to
541 * just this XWalkViewInternal.
545 public void resumeTimers() {
546 if (mContent == null) return;
548 mContent.resumeTimers();
552 * Pause many other things except JavaScript timers inside rendering engine,
553 * like video player, modal dialogs, etc. See {@link #pauseTimers} about pausing
555 * It will be called when the container Activity get paused. It can also be explicitly
556 * called to pause above things.
559 public void onHide() {
560 if (mContent == null || mIsHidden) return;
561 mExtensionManager.onPause();
567 * Resume video player, modal dialogs. Embedders are in charge of calling
568 * this during resuming this activity if they call onHide.
569 * Typically it should be called when the activity for this view is resumed.
570 * It will be called when the container Activity get resumed. It can also be explicitly
571 * called to resume above things.
574 public void onShow() {
575 if (mContent == null || !mIsHidden ) return;
576 mExtensionManager.onResume();
582 * Release internal resources occupied by this XWalkViewInternal.
583 * It will be called when the container Activity get destroyed. It can also be explicitly
584 * called to release resources.
587 public void onDestroy() {
592 * Pass through activity result to XWalkViewInternal. Many internal facilities need this
593 * to handle activity result like JavaScript dialog, Crosswalk extensions, etc.
594 * See <a href="http://developer.android.com/reference/android/app/Activity.html">
595 * android.app.Activity.onActivityResult()</a>.
596 * @param requestCode passed from android.app.Activity.onActivityResult().
597 * @param resultCode passed from android.app.Activity.onActivityResult().
598 * @param data passed from android.app.Activity.onActivityResult().
601 public void onActivityResult(int requestCode, int resultCode, Intent data) {
602 if (mContent == null) return;
603 mExtensionManager.onActivityResult(requestCode, resultCode, data);
604 mContent.onActivityResult(requestCode, resultCode, data);
608 * Pass through intents to XWalkViewInternal. Many internal facilities need this
609 * to receive the intents like web notification. See
610 * <a href="http://developer.android.com/reference/android/app/Activity.html">
611 * android.app.Activity.onNewIntent()</a>.
612 * @param intent passed from android.app.Activity.onNewIntent().
615 public boolean onNewIntent(Intent intent) {
616 if (mContent == null) return false;
617 return mContent.onNewIntent(intent);
621 * Save current internal state of this XWalkViewInternal. This can help restore this state
622 * afterwards restoring.
623 * @param outState the saved state for restoring.
626 public boolean saveState(Bundle outState) {
627 if (mContent == null) return false;
628 mContent.saveState(outState);
633 * Restore the state from the saved bundle data.
634 * @param inState the state saved from saveState().
635 * @return true if it can restore the state.
638 public boolean restoreState(Bundle inState) {
639 if (mContent == null) return false;
640 if (mContent.restoreState(inState) != null) return true;
645 * Get the API version of Crosswalk embedding API.
646 * @return the string of API level.
649 // TODO(yongsheng): make it static?
650 public String getAPIVersion() {
655 * Get the Crosswalk version.
656 * @return the string of Crosswalk.
659 // TODO(yongsheng): make it static?
660 public String getXWalkVersion() {
661 if (mContent == null) return null;
662 return mContent.getXWalkVersion();
666 * Embedders use this to customize their handlers to events/callbacks related
668 * @param client the XWalkUIClientInternal defined by callers.
671 public void setUIClient(XWalkUIClientInternal client) {
672 if (mContent == null) return;
674 mContent.setUIClient(client);
678 * Embedders use this to customize their handlers to events/callbacks related
679 * to resource loading.
680 * @param client the XWalkResourceClientInternal defined by callers.
683 public void setResourceClient(XWalkResourceClientInternal client) {
684 if (mContent == null) return;
686 mContent.setResourceClient(client);
689 // TODO(yongsheng): this is not public.
693 public XWalkSettings getSettings() {
694 if (mContent == null) return null;
696 return mContent.getSettings();
700 * This method is used by Cordova for hacking.
701 * TODO(yongsheng): remove this and related test cases?
705 public void setNetworkAvailable(boolean networkUp) {
706 if (mContent == null) return;
708 mContent.setNetworkAvailable(networkUp);
712 * Enables remote debugging and returns the URL at which the dev tools server is listening
713 * for commands. The allowedUid argument can be used to specify the uid of the process that is
714 * permitted to connect.
715 * TODO(yongsheng): how to enable this in XWalkPreferencesInternal?
719 public String enableRemoteDebugging(int allowedUid) {
720 if (mContent == null) return null;
722 return mContent.enableRemoteDebugging(allowedUid);
726 * It's used for Presentation API.
729 public int getContentID() {
730 if (mContent == null) return -1;
731 return mContent.getRoutingID();
734 boolean canGoBack() {
735 if (mContent == null) return false;
737 return mContent.canGoBack();
741 if (mContent == null) return;
746 boolean canGoForward() {
747 if (mContent == null) return false;
749 return mContent.canGoForward();
753 if (mContent == null) return;
755 mContent.goForward();
758 void clearHistory() {
759 if (mContent == null) return;
761 mContent.clearHistory();
765 if (mContent == null) return;
766 ApplicationStatus.unregisterActivityStateListener(mActivityStateListener);
767 mActivityStateListener = null;
768 mExtensionManager.onDestroy();
770 disableRemoteDebugging();
773 // Enables remote debugging and returns the URL at which the dev tools server is listening
774 // for commands. Only the current process is allowed to connect to the server.
775 String enableRemoteDebugging() {
776 return enableRemoteDebugging(mContext.getApplicationInfo().uid);
779 void disableRemoteDebugging() {
780 if (mContent == null) return;
782 mContent.disableRemoteDebugging();
785 private static void checkThreadSafety() {
786 if (Looper.myLooper() != Looper.getMainLooper()) {
787 Throwable throwable = new Throwable(
788 "Warning: A XWalkViewInternal method was called on thread '" +
789 Thread.currentThread().getName() + "'. " +
790 "All XWalkViewInternal methods must be called on the UI thread. ");
791 throw new RuntimeException(throwable);
795 boolean isOwnerActivityRunning() {
796 int status = ApplicationStatus.getStateForActivity(getActivity());
797 if (status == ActivityState.DESTROYED) return false;
801 void navigateTo(int offset) {
802 if (mContent == null) return;
803 mContent.navigateTo(offset);
806 void setOverlayVideoMode(boolean enabled) {
807 mContent.setOverlayVideoMode(enabled);
810 // Below methods are for test shell and instrumentation tests.
814 public void setXWalkClient(XWalkClient client) {
815 if (mContent == null) return;
817 mContent.setXWalkClient(client);
823 public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
824 if (mContent == null) return;
826 mContent.setXWalkWebChromeClient(client);
832 public void setDownloadListener(DownloadListener listener) {
833 if (mContent == null) return;
835 mContent.setDownloadListener(listener);
841 public void setNavigationHandler(XWalkNavigationHandler handler) {
842 if (mContent == null) return;
844 mContent.setNavigationHandler(handler);
850 public void setNotificationService(XWalkNotificationService service) {
851 if (mContent == null) return;
853 mContent.setNotificationService(service);
860 public boolean dispatchKeyEvent(KeyEvent event) {
861 if (event.getAction() == KeyEvent.ACTION_UP &&
862 event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
863 // If there's navigation happens when app is fullscreen,
864 // the content will still be fullscreen after navigation.
865 // In such case, the back key will exit fullscreen first.
866 if (hasEnteredFullscreen()) {
869 } else if (canGoBack()) {
874 return super.dispatchKeyEvent(event);
877 private void onActivityStateChange(Activity activity, int newState) {
878 assert(getActivity() == activity);
880 case ActivityState.PAUSED:
884 case ActivityState.RESUMED:
888 case ActivityState.DESTROYED: