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 * XWalkView represents an Android view for web apps/pages. Thus most of attributes
33 * for Android view are true for this class. It includes an instance of
34 * android.view.SurfaceView for rendering. Currently limitations for android.view.SurfaceView
35 * also are applied for this class as well, like resizing, retation, transformation and
38 * It provides 2 major callback classes, namely XWalkResourceClient and XWalkUIClient for
39 * listening to the events related resource loading and UI. By default, Crosswalk has an inner
40 * implementation. Callers can override them if like.
42 * Unlike other Android views, this class has to listen to system events like application life
43 * cycle, intents, and activity result. The web engine inside this view need to handle them.
45 * It already includes all newly created Web APIs from Crosswalk like Presentation,
46 * DeviceCapabilities, etc..
48 public class XWalkView extends android.widget.FrameLayout {
50 protected static final String PLAYSTORE_DETAIL_URI = "market://details?id=";
52 private XWalkContent mContent;
53 private Activity mActivity;
54 private Context mContext;
55 private XWalkExtensionManager mExtensionManager;
56 private boolean mIsTimerPaused;
57 private boolean mIsHidden;
59 /** Normal reload mode as default. */
60 public static final int RELOAD_NORMAL = 0;
61 /** Reload mode with bypassing the cache. */
62 public static final int RELOAD_IGNORE_CACHE = 1;
65 * Constructor for inflating via XML.
66 * @param context a Context object used to access application assets.
67 * @param attrs an AttributeSet passed to our parent.
69 public XWalkView(Context context, AttributeSet attrs) {
70 super(context, attrs);
78 * Constructor for Crosswalk runtime. In shared mode, context isi
79 * different from activity. In embedded mode, they're same.
80 * @param context a Context object used to access application assets
81 * @param activity the activity for this XWalkView.
83 public XWalkView(Context context, Activity activity) {
87 // Make sure mActivity is initialized before calling 'init' method.
94 * Get the current activity passed from callers. It's never null.
95 * @return the activity instance passed from callers.
99 public Activity getActivity() {
100 if (mActivity != null) {
102 } else if (getContext() instanceof Activity) {
103 return (Activity)getContext();
106 // Never achieve here.
111 // TODO(yongsheng): we should remove this since we have getContext()?
115 public Context getViewContext() {
119 private void init(Context context, AttributeSet attrs) {
120 // Initialize chromium resources. Assign them the correct ids in
122 XWalkInternalResources.resetIds(context);
124 // Intialize library, paks and others.
126 XWalkViewDelegate.init(this);
127 } catch (UnsatisfiedLinkError e) {
128 final UnsatisfiedLinkError err = e;
129 final Activity activity = getActivity();
130 final String packageName = context.getPackageName();
131 String missingArch = XWalkViewDelegate.isRunningOnIA() ? "Intel" : "ARM";
132 final String message =
133 context.getString(R.string.cpu_arch_mismatch_message, missingArch);
135 AlertDialog.Builder builder = new AlertDialog.Builder(activity);
136 builder.setTitle(R.string.cpu_arch_mismatch_title)
138 .setOnCancelListener(new DialogInterface.OnCancelListener() {
140 public void onCancel(DialogInterface dialog) {
143 }).setPositiveButton(R.string.goto_store_button_label,
144 new DialogInterface.OnClickListener() {
146 public void onClick(DialogInterface dialog, int which) {
147 activity.startActivity(new Intent(Intent.ACTION_VIEW,
148 Uri.parse(PLAYSTORE_DETAIL_URI + packageName)));
151 }).setNeutralButton(R.string.report_feedback_button_label,
152 new DialogInterface.OnClickListener() {
154 public void onClick(DialogInterface dialog, int which) {
155 ApplicationErrorReport report = new ApplicationErrorReport();
156 report.type = ApplicationErrorReport.TYPE_CRASH;
157 report.packageName = report.processName = packageName;
159 ApplicationErrorReport.CrashInfo crash =
160 new ApplicationErrorReport.CrashInfo();
161 crash.exceptionClassName = err.getClass().getSimpleName();
162 crash.exceptionMessage = "CPU architecture mismatch";
163 StringWriter writer = new StringWriter();
164 PrintWriter print = new PrintWriter(writer);
165 err.printStackTrace(print);
166 crash.stackTrace = writer.toString();
167 StackTraceElement stack = err.getStackTrace()[0];
168 crash.throwClassName = stack.getClassName();
169 crash.throwFileName = stack.getFileName();
170 crash.throwLineNumber = stack.getLineNumber();
171 crash.throwMethodName = stack.getMethodName();
173 report.crashInfo = crash;
174 report.systemApp = false;
175 report.time = System.currentTimeMillis();
177 Intent intent = new Intent(Intent.ACTION_APP_ERROR);
178 intent.putExtra(Intent.EXTRA_BUG_REPORT, report);
179 activity.startActivity(intent);
183 builder.create().show();
187 initXWalkContent(context, attrs);
190 private void initXWalkContent(Context context, AttributeSet attrs) {
191 mIsTimerPaused = false;
193 mContent = new XWalkContent(context, attrs, this);
195 new FrameLayout.LayoutParams(
196 FrameLayout.LayoutParams.MATCH_PARENT,
197 FrameLayout.LayoutParams.MATCH_PARENT));
200 // Set default XWalkClientImpl.
201 setXWalkClient(new XWalkClient(this));
202 // Set default XWalkWebChromeClient and DownloadListener. The default actions
203 // are provided via the following clients if special actions are not needed.
204 setXWalkWebChromeClient(new XWalkWebChromeClient(this));
206 // Set with internal implementation. Could be overwritten by embedders'
208 setUIClient(new XWalkUIClient(this));
209 setResourceClient(new XWalkResourceClient(this));
211 setDownloadListener(new XWalkDownloadListenerImpl(context));
212 setNavigationHandler(new XWalkNavigationHandlerImpl(context));
213 setNotificationService(new XWalkNotificationServiceImpl(context, this));
215 // Enable xwalk extension mechanism and start load extensions here.
216 // Note that it has to be after above initialization.
217 mExtensionManager = new XWalkExtensionManager(context, getActivity());
218 mExtensionManager.loadExtensions();
222 * Load a web page/app from a given base URL or a content.
223 * If url is null or empty and content is null or empty, then this function
225 * If content is not null, load the web page/app from the content.
226 * If content is not null and the url is not set, return "about:blank" ifi
227 * calling {@link XWalkView#getUrl()}.
228 * If content is null, try to load the content from the url.
230 * It supports URL schemes like 'http:', 'https:' and 'file:'.
231 * It can also load files from Android assets, e.g. 'file:///android_asset/'.
232 * @param url the url for web page/app.
233 * @param content the content for the web page/app. Could be empty.
235 public void load(String url, String content) {
236 if (mContent == null) return;
238 mContent.loadUrl(url, content);
242 * Load a web app from a given manifest.json file. If content is not null,
243 * load the manifest.json from the content. If content is null, try to load
244 * the manifest.json from the url. Note that url should not be null if the
245 * launched path defined in manifest.json is relative.
247 * It supports URL schemes like 'http:', 'https:' and 'file:'.
248 * It can also load files from Android assets, e.g. 'file:///android_asset/'.
249 * @param url the url for manifest.json.
250 * @param content the content for manifest.json.
252 public void loadAppFromManifest(String url, String content) {
253 if (mContent == null) return;
255 mContent.loadAppFromManifest(url, content);
259 * Reload a web app with a given mode.
260 * @param mode the reload mode.
262 public void reload(int mode) {
263 if (mContent == null) return;
265 mContent.reload(mode);
269 * Stop current loading progress.
271 public void stopLoading() {
272 if (mContent == null) return;
274 mContent.stopLoading();
278 * Get the url of current web page/app. This may be different from what's passed
280 * @return the url for current web page/app.
282 public String getUrl() {
283 if (mContent == null) return null;
285 return mContent.getUrl();
289 * Get the title of current web page/app. This may be different from what's passed
291 * @return the title for current web page/app.
293 public String getTitle() {
294 if (mContent == null) return null;
296 return mContent.getTitle();
300 * Get the original url specified by caller.
301 * @return the original url.
303 public String getOriginalUrl() {
304 if (mContent == null) return null;
306 return mContent.getOriginalUrl();
310 * Get the navigation history for current XWalkView. It's synchronized with
311 * this XWalkView if any backward/forward and navigation operations.
312 * @return the navigation history.
314 public XWalkNavigationHistory getNavigationHistory() {
315 if (mContent == null) return null;
317 return mContent.getNavigationHistory();
321 * Injects the supplied Java object into this XWalkView.
322 * Each method defined in the class of the object should be
323 * marked with {@link JavascriptInterface} if it's called by JavaScript.
324 * @param object the supplied Java object, called by JavaScript.
325 * @param name the name injected in JavaScript.
327 public void addJavascriptInterface(Object object, String name) {
328 if (mContent == null) return;
330 mContent.addJavascriptInterface(object, name);
334 * Evaluate a fragment of JavaScript code and get the result via callback.
335 * @param script the JavaScript string.
336 * @param callback the callback to handle the evaluated result.
338 public void evaluateJavascript(String script, ValueCallback<String> callback) {
339 if (mContent == null) return;
341 mContent.evaluateJavascript(script, callback);
345 * Clear the resource cache. Note that the cache is per-application, so this
346 * will clear the cache for all XWalkViews used.
347 * @param includeDiskFiles indicate whether to clear disk files for cache.
349 public void clearCache(boolean includeDiskFiles) {
350 if (mContent == null) return;
352 mContent.clearCache(includeDiskFiles);
356 * Indicate that a HTML element is occupying the whole screen.
357 * @return true if any HTML element is occupying the whole screen.
359 public boolean hasEnteredFullscreen() {
360 if (mContent == null) return false;
362 return mContent.hasEnteredFullscreen();
366 * Leave fullscreen mode if it's. Do nothing if it's not
369 public void leaveFullscreen() {
370 if (mContent == null) return;
372 mContent.exitFullscreen();
376 * Pause all layout, parsing and JavaScript timers for all XWalkView instances.
377 * Typically it should be called when the activity for this view is paused,
378 * and accordingly {@link #resumeTimers} should be called when the activity
381 * Note that it will globally impact all XWalkView instances, not limited to
382 * just this XWalkView.
384 public void pauseTimers() {
385 if (mContent == null || mIsTimerPaused) return;
387 mContent.pauseTimers();
388 mIsTimerPaused = true;
392 * Resume all layout, parsing and JavaScript timers for all XWalkView instances.
393 * Typically it should be called when the activity for this view is resumed.
395 * Note that it will globally impact all XWalkView instances, not limited to
396 * just this XWalkView.
398 public void resumeTimers() {
399 if (mContent == null || !mIsTimerPaused) return;
401 mContent.resumeTimers();
402 mIsTimerPaused = false;
406 * Pause many other things except JavaScript timers inside rendering engine,
407 * like video player, modal dialogs, etc. See {@link #pauseTimers} about pausing
409 * Typically it should be called when the activity for this view is paused.
411 public void onHide() {
412 if (mContent == null || mIsHidden) return;
413 mExtensionManager.onPause();
419 * Resume video player, modal dialogs. Embedders are in charge of calling
420 * this during resuming this activity if they call onHide.
421 * Typically it should be called when the activity for this view is resumed.
423 public void onShow() {
424 if (mContent == null || !mIsHidden ) return;
425 mExtensionManager.onResume();
431 * Release internal resources occupied by this XWalkView.
433 public void onDestroy() {
438 * Pass through activity result to XWalkView. Many internal facilities need this
439 * to handle activity result like JavaScript dialog, Crosswalk extensions, etc.
440 * See android.app.Activity.onActivityResult().
441 * @param requestCode passed from android.app.Activity.onActivityResult().
442 * @param resultCode passed from android.app.Activity.onActivityResult().
443 * @param data passed from android.app.Activity.onActivityResult().
445 public void onActivityResult(int requestCode, int resultCode, Intent data) {
446 if (mContent == null) return;
447 mExtensionManager.onActivityResult(requestCode, resultCode, data);
448 mContent.onActivityResult(requestCode, resultCode, data);
452 * Pass through intents to XWalkView. Many internal facilities need this
453 * to receive the intents like web notification. See
454 * android.app.Activity.onNewIntent().
455 * @param intent passed from android.app.Activity.onNewIntent().
457 public boolean onNewIntent(Intent intent) {
458 if (mContent == null) return false;
459 return mContent.onNewIntent(intent);
463 * Save current internal state of this XWalkView. This can help restore this state
464 * afterwards restoring.
465 * @param outState the saved state for restoring.
467 public boolean saveState(Bundle outState) {
468 if (mContent == null) return false;
469 mContent.saveState(outState);
474 * Restore the state from the saved bundle data.
475 * @param inState the state saved from saveState().
476 * @return true if it can restore the state.
478 public boolean restoreState(Bundle inState) {
479 if (mContent == null) return false;
480 if (mContent.restoreState(inState) != null) return true;
485 * Get the API version of Crosswalk embedding API.
486 * @return the string of API level.
488 // TODO(yongsheng): make it static?
489 public String getAPIVersion() {
494 * Get the Crosswalk version.
495 * @return the string of Crosswalk.
497 // TODO(yongsheng): make it static?
498 public String getXWalkVersion() {
499 if (mContent == null) return null;
500 return mContent.getXWalkVersion();
504 * Embedders use this to customize their handlers to events/callbacks related
506 * @param client the XWalkUIClient defined by callers.
508 public void setUIClient(XWalkUIClient client) {
509 if (mContent == null) return;
511 mContent.setUIClient(client);
515 * Embedders use this to customize their handlers to events/callbacks related
516 * to resource loading.
517 * @param client the XWalkResourceClient defined by callers.
519 public void setResourceClient(XWalkResourceClient client) {
520 if (mContent == null) return;
522 mContent.setResourceClient(client);
526 * Inherit from android.view.View. This class needs to handle some keys like
528 * @param keyCode passed from android.view.View.onKeyUp().
529 * @param event passed from android.view.View.onKeyUp().
532 public boolean onKeyUp(int keyCode, KeyEvent event) {
533 if (keyCode == KeyEvent.KEYCODE_BACK) {
534 // If there's navigation happens when app is fullscreen,
535 // the content will still be fullscreen after navigation.
536 // In such case, the back key will exit fullscreen first.
537 if (hasEnteredFullscreen()) {
540 } else if (canGoBack()) {
548 // TODO(yongsheng): this is not public.
552 public XWalkSettings getSettings() {
553 if (mContent == null) return null;
555 return mContent.getSettings();
559 * This method is used by Cordova for hacking.
560 * TODO(yongsheng): remove this and related test cases?
564 public void setNetworkAvailable(boolean networkUp) {
565 if (mContent == null) return;
567 mContent.setNetworkAvailable(networkUp);
571 * Enables remote debugging and returns the URL at which the dev tools server is listening
572 * for commands. The allowedUid argument can be used to specify the uid of the process that is
573 * permitted to connect.
574 * TODO(yongsheng): how to enable this in XWalkPreferences?
578 public String enableRemoteDebugging(int allowedUid) {
579 if (mContent == null) return null;
581 return mContent.enableRemoteDebugging(allowedUid);
585 * It's used for Presentation API.
588 public int getContentID() {
589 if (mContent == null) return -1;
590 return mContent.getRoutingID();
593 boolean canGoBack() {
594 if (mContent == null) return false;
596 return mContent.canGoBack();
600 if (mContent == null) return;
605 boolean canGoForward() {
606 if (mContent == null) return false;
608 return mContent.canGoForward();
612 if (mContent == null) return;
614 mContent.goForward();
617 void clearHistory() {
618 if (mContent == null) return;
620 mContent.clearHistory();
624 if (mContent == null) return;
625 mExtensionManager.onDestroy();
627 disableRemoteDebugging();
630 // Enables remote debugging and returns the URL at which the dev tools server is listening
631 // for commands. Only the current process is allowed to connect to the server.
632 String enableRemoteDebugging() {
633 return enableRemoteDebugging(mContext.getApplicationInfo().uid);
636 void disableRemoteDebugging() {
637 if (mContent == null) return;
639 mContent.disableRemoteDebugging();
642 private static void checkThreadSafety() {
643 if (Looper.myLooper() != Looper.getMainLooper()) {
644 Throwable throwable = new Throwable(
645 "Warning: A XWalkView method was called on thread '" +
646 Thread.currentThread().getName() + "'. " +
647 "All XWalkView methods must be called on the UI thread. ");
648 throw new RuntimeException(throwable);
652 boolean isOwnerActivityRunning() {
653 int status = ApplicationStatus.getStateForActivity(getActivity());
654 if (status == ActivityState.DESTROYED) return false;
658 void navigateTo(int offset) {
659 if (mContent == null) return;
660 mContent.navigateTo(offset);
663 void setOverlayVideoMode(boolean enabled) {
664 mContent.setOverlayVideoMode(enabled);
667 // Below methods are for test shell and instrumentation tests.
671 public void setXWalkClient(XWalkClient client) {
672 if (mContent == null) return;
674 mContent.setXWalkClient(client);
680 public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
681 if (mContent == null) return;
683 mContent.setXWalkWebChromeClient(client);
689 public void setDownloadListener(DownloadListener listener) {
690 if (mContent == null) return;
692 mContent.setDownloadListener(listener);
698 public void setNavigationHandler(XWalkNavigationHandler handler) {
699 if (mContent == null) return;
701 mContent.setNavigationHandler(handler);
707 public void setNotificationService(XWalkNotificationService service) {
708 if (mContent == null) return;
710 mContent.setNotificationService(service);