1847d9838251a9d982a54e0c8496c4cfed9de429
[platform/framework/web/crosswalk.git] / src / xwalk / runtime / android / core_internal / src / org / xwalk / core / internal / XWalkViewInternal.java
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.
4
5 package org.xwalk.core.internal;
6
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;
23
24 import java.io.File;
25 import java.io.PrintWriter;
26 import java.io.StringWriter;
27 import java.lang.ref.WeakReference;
28
29 import org.chromium.base.ActivityState;
30 import org.chromium.base.ApplicationStatus;
31 import org.chromium.base.ApplicationStatus.ActivityStateListener;
32 import org.chromium.base.CommandLine;
33
34 import org.xwalk.core.internal.extension.XWalkExtensionManager;
35 import org.xwalk.core.internal.extension.XWalkPathHelper;
36
37 /**
38  * <p>XWalkViewInternal represents an Android view for web apps/pages. Thus most of attributes
39  * for Android view are valid for this class. Since it internally uses
40  * <a href="http://developer.android.com/reference/android/view/SurfaceView.html">
41  * android.view.SurfaceView</a> for rendering web pages by default, it can't be resized,
42  * rotated, transformed and animated due to the limitations of SurfaceView.
43  * Alternatively, if the preference key {@link XWalkPreferencesInternal#ANIMATABLE_XWALK_VIEW}
44  * is set to True, XWalkViewInternal can be transformed and animated because
45  * <a href="http://developer.android.com/reference/android/view/TextureView.html">
46  * TextureView</a> is intentionally used to render web pages for animation support.
47  * Besides, XWalkViewInternal won't be rendered if it's invisible.</p>
48  *
49  * <p>XWalkViewInternal needs hardware acceleration to render web pages. As a result, the
50  * AndroidManifest.xml of the caller's app must be appended with the attribute
51  * "android:hardwareAccelerated" and its value must be set as "true".</p>
52  * <pre>
53  * &lt;application android:name="android.app.Application" android:label="XWalkUsers"
54  *     android:hardwareAccelerated="true"&gt;
55  * </pre>
56  *
57  * <p>Crosswalk provides 2 major callback classes, namely {@link XWalkResourceClientInternal} and
58  * {@link XWalkUIClientInternal} for listening to the events related to resource loading and UI.
59  * By default, Crosswalk has a default implementation. Callers can override them if needed.</p>
60  *
61  * <p>Unlike other Android views, this class has to listen to system events like intents and activity result.
62  * The web engine inside this view need to get and handle them.
63  * With contianer activity's lifecycle change, XWalkViewInternal will pause all timers and other
64  * components like videos when activity paused, resume back them when activity resumed.
65  * When activity is about to destroy, XWalkViewInternal will destroy itself as well.
66  * Embedders can also call onHide() and pauseTimers() to explicitly pause XWalkViewInternal.
67  * Similarily with onShow(), resumeTimers() and onDestroy().
68  *
69  * For example:</p>
70  *
71  * <pre>
72  *   import android.app.Activity;
73  *   import android.os.Bundle;
74  *
75  *   import org.xwalk.core.internal.XWalkResourceClientInternal;
76  *   import org.xwalk.core.internal.XWalkUIClientInternal;
77  *   import org.xwalk.core.internal.XWalkViewInternal;
78  *
79  *   public class MyActivity extends Activity {
80  *       XWalkViewInternal mXwalkView;
81  *
82  *       class MyResourceClient extends XWalkResourceClientInternal {
83  *           MyResourceClient(XWalkViewInternal view) {
84  *               super(view);
85  *           }
86  *
87  *           &#64;Override
88  *           WebResourceResponse shouldInterceptLoadRequest(XWalkViewInternal view, String url) {
89  *               // Handle it here.
90  *               ...
91  *           }
92  *       }
93  *
94  *       class MyUIClient extends XWalkUIClientInternal {
95  *           MyUIClient(XWalkViewInternal view) {
96  *               super(view);
97  *           }
98  *
99  *           &#64;Override
100  *           void onFullscreenToggled(XWalkViewInternal view, String url) {
101  *               // Handle it here.
102  *               ...
103  *           }
104  *       }
105  *
106  *       &#64;Override
107  *       protected void onCreate(Bundle savedInstanceState) {
108  *           mXwalkView = new XWalkViewInternal(this, null);
109  *           setContentView(mXwalkView);
110  *           mXwalkView.setResourceClient(new MyResourceClient(mXwalkView));
111  *           mXwalkView.setUIClient(new MyUIClient(mXwalkView));
112  *           mXwalkView.load("http://www.crosswalk-project.org", null);
113  *       }
114  *
115  *       &#64;Override
116  *       protected void onActivityResult(int requestCode, int resultCode, Intent data) {
117  *           if (mXwalkView != null) {
118  *               mXwalkView.onActivityResult(requestCode, resultCode, data);
119  *           }
120  *       }
121  *
122  *       &#64;Override
123  *       protected void onNewIntent(Intent intent) {
124  *           if (mXwalkView != null) {
125  *               mXwalkView.onNewIntent(intent);
126  *           }
127  *       }
128  *   }
129  * </pre>
130  */
131 @XWalkAPI(extendClass = FrameLayout.class, createExternally = true)
132 public class XWalkViewInternal extends android.widget.FrameLayout {
133
134     private class XWalkActivityStateListener implements ActivityStateListener {
135         WeakReference<XWalkViewInternal> mXWalkViewRef;
136
137         XWalkActivityStateListener(XWalkViewInternal view) {
138             mXWalkViewRef = new WeakReference<XWalkViewInternal>(view);
139         }
140
141         @Override
142         public void onActivityStateChange(Activity activity, int newState) {
143             XWalkViewInternal view = mXWalkViewRef.get();
144             if (view == null) return;
145             view.onActivityStateChange(activity, newState);
146         }
147     }
148
149     static final String PLAYSTORE_DETAIL_URI = "market://details?id=";
150
151     private XWalkContent mContent;
152     private Activity mActivity;
153     private Context mContext;
154     private XWalkExtensionManager mExtensionManager;
155     private boolean mIsHidden;
156     private XWalkActivityStateListener mActivityStateListener;
157
158     /**
159      * Normal reload mode as default.
160      * @since 1.0
161      */
162     @XWalkAPI
163     public static final int RELOAD_NORMAL = 0;
164     /**
165      * Reload mode with bypassing the cache.
166      * @since 1.0
167      */
168     @XWalkAPI
169     public static final int RELOAD_IGNORE_CACHE = 1;
170
171     /**
172      * Constructor for inflating via XML.
173      * @param context  a Context object used to access application assets.
174      * @param attrs    an AttributeSet passed to our parent.
175      * @since 1.0
176      */
177     @XWalkAPI(preWrapperLines = {
178                   "        super(${param1}, ${param2});"},
179               postWrapperLines = {
180                   "        addView((FrameLayout)bridge, new FrameLayout.LayoutParams(",
181                   "                FrameLayout.LayoutParams.MATCH_PARENT,",
182                   "                FrameLayout.LayoutParams.MATCH_PARENT));"})
183     public XWalkViewInternal(Context context, AttributeSet attrs) {
184         super(context, attrs);
185
186         checkThreadSafety();
187         mContext = context;
188         init(context, attrs);
189     }
190
191     /**
192      * Constructor for Crosswalk runtime. In shared mode, context isi
193      * different from activity. In embedded mode, they're same.
194      * @param context  a Context object used to access application assets
195      * @param activity the activity for this XWalkViewInternal.
196      * @since 1.0
197      */
198     @XWalkAPI(preWrapperLines = {
199                   "        super(${param1}, null);"},
200               postWrapperLines = {
201                   "        addView((FrameLayout)bridge, new FrameLayout.LayoutParams(",
202                   "                FrameLayout.LayoutParams.MATCH_PARENT,",
203                   "                FrameLayout.LayoutParams.MATCH_PARENT));"})
204     public XWalkViewInternal(Context context, Activity activity) {
205         super(context, null);
206         checkThreadSafety();
207
208         // Make sure mActivity is initialized before calling 'init' method.
209         mActivity = activity;
210         mContext = context;
211         init(context, null);
212     }
213
214     /**
215      * Get the current activity passed from callers. It's never null.
216      * @return the activity instance passed from callers.
217      *
218      * @hide
219      */
220     public Activity getActivity() {
221         if (mActivity != null) {
222             return mActivity;
223         } else if (getContext() instanceof Activity) {
224             return (Activity)getContext();
225         }
226
227         // Never achieve here.
228         assert(false);
229         return null;
230     }
231
232     // TODO(yongsheng): we should remove this since we have getContext()?
233     /**
234      * @hide
235      */
236     public Context getViewContext() {
237         return mContext;
238     }
239
240     private void init(Context context, AttributeSet attrs) {
241         // Initialize chromium resources. Assign them the correct ids in
242         // xwalk core.
243         XWalkInternalResources.resetIds(context);
244
245         // Intialize library, paks and others.
246         try {
247             XWalkViewDelegate.init(this);
248             mActivityStateListener = new XWalkActivityStateListener(this);
249             ApplicationStatus.registerStateListenerForActivity(
250                     mActivityStateListener, getActivity());
251         } catch (Throwable e) {
252             // Try to find if there is UnsatisfiedLinkError in the cause chain of the met Throwable.
253             Throwable linkError = e;
254             while (true) {
255                 if (linkError == null) throw new RuntimeException(e);
256                 if (linkError instanceof UnsatisfiedLinkError) break;
257                 if (linkError.getCause() == null ||
258                         linkError.getCause().equals(linkError)) {
259                     throw new RuntimeException(e);
260                 }
261                 linkError = linkError.getCause();
262             }
263             final UnsatisfiedLinkError err = (UnsatisfiedLinkError) linkError;
264             final Activity activity = getActivity();
265             final String packageName = context.getPackageName();
266             String missingArch = XWalkViewDelegate.isRunningOnIA() ? "Intel" : "ARM";
267             final String message =
268                     context.getString(R.string.cpu_arch_mismatch_message, missingArch);
269
270             AlertDialog.Builder builder = new AlertDialog.Builder(activity);
271             builder.setTitle(R.string.cpu_arch_mismatch_title)
272                     .setMessage(message)
273                     .setOnCancelListener(new DialogInterface.OnCancelListener() {
274                         @Override
275                         public void onCancel(DialogInterface dialog) {
276                             activity.finish();
277                         }
278                     }).setPositiveButton(R.string.goto_store_button_label,
279                             new DialogInterface.OnClickListener() {
280                         @Override
281                         public void onClick(DialogInterface dialog, int which) {
282                             activity.startActivity(new Intent(Intent.ACTION_VIEW,
283                                     Uri.parse(PLAYSTORE_DETAIL_URI + packageName)));
284                             activity.finish();
285                         }
286                     }).setNeutralButton(R.string.report_feedback_button_label,
287                             new DialogInterface.OnClickListener() {
288                         @Override
289                         public void onClick(DialogInterface dialog, int which) {
290                             ApplicationErrorReport report = new ApplicationErrorReport();
291                             report.type = ApplicationErrorReport.TYPE_CRASH;
292                             report.packageName = report.processName = packageName;
293
294                             ApplicationErrorReport.CrashInfo crash =
295                                     new ApplicationErrorReport.CrashInfo();
296                             crash.exceptionClassName = err.getClass().getSimpleName();
297                             crash.exceptionMessage = "CPU architecture mismatch";
298                             StringWriter writer = new StringWriter();
299                             PrintWriter print = new PrintWriter(writer);
300                             err.printStackTrace(print);
301                             crash.stackTrace = writer.toString();
302                             StackTraceElement stack = err.getStackTrace()[0];
303                             crash.throwClassName = stack.getClassName();
304                             crash.throwFileName = stack.getFileName();
305                             crash.throwLineNumber = stack.getLineNumber();
306                             crash.throwMethodName = stack.getMethodName();
307
308                             report.crashInfo = crash;
309                             report.systemApp = false;
310                             report.time = System.currentTimeMillis();
311
312                             Intent intent = new Intent(Intent.ACTION_APP_ERROR);
313                             intent.putExtra(Intent.EXTRA_BUG_REPORT, report);
314                             activity.startActivity(intent);
315                             activity.finish();
316                         }
317                     });
318             builder.create().show();
319             return;
320         }
321
322         initXWalkContent(context, attrs);
323     }
324
325     private void initXWalkContent(Context context, AttributeSet attrs) {
326         mIsHidden = false;
327         mContent = new XWalkContent(context, attrs, this);
328         addView(mContent,
329                 new FrameLayout.LayoutParams(
330                         FrameLayout.LayoutParams.MATCH_PARENT,
331                         FrameLayout.LayoutParams.MATCH_PARENT));
332
333
334         // Set default XWalkClientImpl.
335         setXWalkClient(new XWalkClient(this));
336         // Set default XWalkWebChromeClient and DownloadListener. The default actions
337         // are provided via the following clients if special actions are not needed.
338         setXWalkWebChromeClient(new XWalkWebChromeClient(this));
339
340         // Set with internal implementation. Could be overwritten by embedders'
341         // setting.
342         setUIClient(new XWalkUIClientInternal(this));
343         setResourceClient(new XWalkResourceClientInternal(this));
344
345         setDownloadListener(new XWalkDownloadListenerImpl(context));
346         setNavigationHandler(new XWalkNavigationHandlerImpl(context));
347         setNotificationService(new XWalkNotificationServiceImpl(context, this));
348
349         if (!CommandLine.getInstance().hasSwitch("disable-xwalk-extensions")) {
350             // Enable xwalk extension mechanism and start load extensions here.
351             // Note that it has to be after above initialization.
352             mExtensionManager = new XWalkExtensionManager(context, getActivity());
353             mExtensionManager.loadExtensions();
354         }
355
356         XWalkPathHelper.initialize();
357         XWalkPathHelper.setCacheDirectory(
358                 mContext.getApplicationContext().getCacheDir().getPath());
359
360         String state = Environment.getExternalStorageState();
361         if (Environment.MEDIA_MOUNTED.equals(state) ||
362                 Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
363             File extCacheDir =  mContext.getApplicationContext().getExternalCacheDir();
364             if (null != extCacheDir) {
365                 XWalkPathHelper.setExternalCacheDirectory(extCacheDir.getPath());
366             }
367         }
368     }
369
370     /**
371      * Load a web page/app from a given base URL or a content.
372      * If url is null or empty and content is null or empty, then this function
373      * will do nothing.
374      * If content is not null, load the web page/app from the content.
375      * If content is not null and the url is not set, return "about:blank" ifi
376      * calling {@link XWalkViewInternal#getUrl()}.
377      * If content is null, try to load the content from the url.
378      *
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 web page/app.
382      * @param content the content for the web page/app. Could be empty.
383      * @since 1.0
384      */
385     @XWalkAPI
386     public void load(String url, String content) {
387         if (mContent == null) return;
388         checkThreadSafety();
389         mContent.loadUrl(url, content);
390     }
391
392     /**
393      * Load a web app from a given manifest.json file. If content is not null,
394      * load the manifest.json from the content. If content is null, try to load
395      * the manifest.json from the url. Note that url should not be null if the
396      * launched path defined in manifest.json is relative.
397      *
398      * It supports URL schemes like 'http:', 'https:' and 'file:'.
399      * It can also load files from Android assets, e.g. 'file:///android_asset/'.
400      * @param url the url for manifest.json.
401      * @param content the content for manifest.json.
402      * @since 1.0
403      */
404     @XWalkAPI
405     public void loadAppFromManifest(String url, String content) {
406         if (mContent == null) return;
407         checkThreadSafety();
408         mContent.loadAppFromManifest(url, content);
409     }
410
411     /**
412      * Reload a web app with a given mode.
413      * @param mode the reload mode.
414      * @since 1.0
415      */
416     @XWalkAPI
417     public void reload(int mode) {
418         if (mContent == null) return;
419         checkThreadSafety();
420         mContent.reload(mode);
421     }
422
423     /**
424      * Stop current loading progress.
425      * @since 1.0
426      */
427     @XWalkAPI
428     public void stopLoading() {
429         if (mContent == null) return;
430         checkThreadSafety();
431         mContent.stopLoading();
432     }
433
434     /**
435      * Get the url of current web page/app. This may be different from what's passed
436      * by caller.
437      * @return the url for current web page/app.
438      * @since 1.0
439      */
440     @XWalkAPI
441     public String getUrl() {
442         if (mContent == null) return null;
443         checkThreadSafety();
444         return mContent.getUrl();
445     }
446
447     /**
448      * Get the title of current web page/app. This may be different from what's passed
449      * by caller.
450      * @return the title for current web page/app.
451      * @since 1.0
452      */
453     @XWalkAPI
454     public String getTitle() {
455         if (mContent == null) return null;
456         checkThreadSafety();
457         return mContent.getTitle();
458     }
459
460     /**
461      * Get the original url specified by caller.
462      * @return the original url.
463      * @since 1.0
464      */
465     @XWalkAPI
466     public String getOriginalUrl() {
467         if (mContent == null) return null;
468         checkThreadSafety();
469         return mContent.getOriginalUrl();
470     }
471
472     /**
473      * Get the navigation history for current XWalkViewInternal. It's synchronized with
474      * this XWalkViewInternal if any backward/forward and navigation operations.
475      * @return the navigation history.
476      * @since 1.0
477      */
478     @XWalkAPI
479     public XWalkNavigationHistoryInternal getNavigationHistory() {
480         if (mContent == null) return null;
481         checkThreadSafety();
482         return mContent.getNavigationHistory();
483     }
484
485     /**
486      * Injects the supplied Java object into this XWalkViewInternal.
487      * Each method defined in the class of the object should be
488      * marked with {@link JavascriptInterface} if it's called by JavaScript.
489      * @param object the supplied Java object, called by JavaScript.
490      * @param name the name injected in JavaScript.
491      * @since 1.0
492      */
493     @XWalkAPI
494     public void addJavascriptInterface(Object object, String name) {
495         if (mContent == null) return;
496         checkThreadSafety();
497         mContent.addJavascriptInterface(object, name);
498     }
499
500     /**
501      * Evaluate a fragment of JavaScript code and get the result via callback.
502      * @param script the JavaScript string.
503      * @param callback the callback to handle the evaluated result.
504      * @since 1.0
505      */
506     @XWalkAPI
507     public void evaluateJavascript(String script, ValueCallback<String> callback) {
508         if (mContent == null) return;
509         checkThreadSafety();
510         mContent.evaluateJavascript(script, callback);
511     }
512
513     /**
514      * Clear the resource cache. Note that the cache is per-application, so this
515      * will clear the cache for all XWalkViews used.
516      * @param includeDiskFiles indicate whether to clear disk files for cache.
517      * @since 1.0
518      */
519     @XWalkAPI
520     public void clearCache(boolean includeDiskFiles) {
521         if (mContent == null) return;
522         checkThreadSafety();
523         mContent.clearCache(includeDiskFiles);
524     }
525
526     /**
527      * Indicate that a HTML element is occupying the whole screen.
528      * @return true if any HTML element is occupying the whole screen.
529      * @since 1.0
530      */
531     @XWalkAPI
532     public boolean hasEnteredFullscreen() {
533         if (mContent == null) return false;
534         checkThreadSafety();
535         return mContent.hasEnteredFullscreen();
536     }
537
538     /**
539      * Leave fullscreen mode if it's. Do nothing if it's not
540      * in fullscreen.
541      * @since 1.0
542      */
543     @XWalkAPI
544     public void leaveFullscreen() {
545         if (mContent == null) return;
546         checkThreadSafety();
547         mContent.exitFullscreen();
548     }
549
550     /**
551      * Pause all layout, parsing and JavaScript timers for all XWalkViewInternal instances.
552      * It will be called when the container Activity get paused. It can also be explicitly
553      * called to pause timers.
554      *
555      * Note that it will globally impact all XWalkViewInternal instances, not limited to
556      * just this XWalkViewInternal.
557      *
558      * @since 1.0
559      */
560     @XWalkAPI
561     public void pauseTimers() {
562         if (mContent == null) return;
563         checkThreadSafety();
564         mContent.pauseTimers();
565     }
566
567     /**
568      * Resume all layout, parsing and JavaScript timers for all XWalkViewInternal instances.
569      * It will be called when the container Activity get resumed. It can also be explicitly
570      * called to resume timers.
571      *
572      * Note that it will globally impact all XWalkViewInternal instances, not limited to
573      * just this XWalkViewInternal.
574      *
575      * @since 1.0
576      */
577     @XWalkAPI
578     public void resumeTimers() {
579         if (mContent == null) return;
580         checkThreadSafety();
581         mContent.resumeTimers();
582     }
583
584     /**
585      * Pause many other things except JavaScript timers inside rendering engine,
586      * like video player, modal dialogs, etc. See {@link #pauseTimers} about pausing
587      * JavaScript timers.
588      * It will be called when the container Activity get paused. It can also be explicitly
589      * called to pause above things.
590      * @since 1.0
591      */
592     @XWalkAPI
593     public void onHide() {
594         if (mContent == null || mIsHidden) return;
595         mContent.onPause();
596         mIsHidden = true;
597     }
598
599     /**
600      * Resume video player, modal dialogs. Embedders are in charge of calling
601      * this during resuming this activity if they call onHide.
602      * Typically it should be called when the activity for this view is resumed.
603      * It will be called when the container Activity get resumed. It can also be explicitly
604      * called to resume above things.
605      * @since 1.0
606      */
607     @XWalkAPI
608     public void onShow() {
609         if (mContent == null || !mIsHidden ) return;
610         mContent.onResume();
611         mIsHidden = false;
612     }
613
614     /**
615      * Release internal resources occupied by this XWalkViewInternal.
616      * It will be called when the container Activity get destroyed. It can also be explicitly
617      * called to release resources.
618      * @since 1.0
619      */
620     @XWalkAPI
621     public void onDestroy() {
622         destroy();
623     }
624
625     /**
626      * Pass through activity result to XWalkViewInternal. Many internal facilities need this
627      * to handle activity result like JavaScript dialog, Crosswalk extensions, etc.
628      * See <a href="http://developer.android.com/reference/android/app/Activity.html">
629      * android.app.Activity.onActivityResult()</a>.
630      * @param requestCode passed from android.app.Activity.onActivityResult().
631      * @param resultCode passed from android.app.Activity.onActivityResult().
632      * @param data passed from android.app.Activity.onActivityResult().
633      * @since 1.0
634      */
635     @XWalkAPI
636     public void onActivityResult(int requestCode, int resultCode, Intent data) {
637         if (mContent == null) return;
638         if (null != mExtensionManager)
639                 mExtensionManager.onActivityResult(requestCode, resultCode, data);
640         mContent.onActivityResult(requestCode, resultCode, data);
641     }
642
643     /**
644      * Pass through intents to XWalkViewInternal. Many internal facilities need this
645      * to receive the intents like web notification. See
646      * <a href="http://developer.android.com/reference/android/app/Activity.html">
647      * android.app.Activity.onNewIntent()</a>.
648      * @param intent passed from android.app.Activity.onNewIntent().
649      * @since 1.0
650      */
651     @XWalkAPI
652     public boolean onNewIntent(Intent intent) {
653         if (mContent == null) return false;
654         return mContent.onNewIntent(intent);
655     }
656
657     /**
658      * Save current internal state of this XWalkViewInternal. This can help restore this state
659      * afterwards restoring.
660      * @param outState the saved state for restoring.
661      * @since 1.0
662      */
663     @XWalkAPI
664     public boolean saveState(Bundle outState) {
665         if (mContent == null) return false;
666         mContent.saveState(outState);
667         return true;
668     }
669
670     /**
671      * Restore the state from the saved bundle data.
672      * @param inState the state saved from saveState().
673      * @return true if it can restore the state.
674      * @since 1.0
675      */
676     @XWalkAPI
677     public boolean restoreState(Bundle inState) {
678         if (mContent == null) return false;
679         if (mContent.restoreState(inState) != null) return true;
680         return false;
681     }
682
683     /**
684      * Get the API version of Crosswalk embedding API.
685      * @return the string of API level.
686      * @since 1.0
687      */
688     // TODO(yongsheng): make it static?
689     @XWalkAPI
690     public String getAPIVersion() {
691         return "2.1";
692     }
693
694     /**
695      * Get the Crosswalk version.
696      * @return the string of Crosswalk.
697      * @since 1.0
698      */
699     // TODO(yongsheng): make it static?
700     @XWalkAPI
701     public String getXWalkVersion() {
702         if (mContent == null) return null;
703         return mContent.getXWalkVersion();
704     }
705
706     /**
707      * Embedders use this to customize their handlers to events/callbacks related
708      * to UI.
709      * @param client the XWalkUIClientInternal defined by callers.
710      * @since 1.0
711      */
712     @XWalkAPI
713     public void setUIClient(XWalkUIClientInternal client) {
714         if (mContent == null) return;
715         checkThreadSafety();
716         mContent.setUIClient(client);
717     }
718
719     /**
720      * Embedders use this to customize their handlers to events/callbacks related
721      * to resource loading.
722      * @param client the XWalkResourceClientInternal defined by callers.
723      * @since 1.0
724      */
725     @XWalkAPI
726     public void setResourceClient(XWalkResourceClientInternal client) {
727         if (mContent == null) return;
728         checkThreadSafety();
729         mContent.setResourceClient(client);
730     }
731
732     // TODO(yongsheng): this is not public.
733     /**
734      * @hide
735      */
736     public XWalkSettings getSettings() {
737         if (mContent == null) return null;
738         checkThreadSafety();
739         return mContent.getSettings();
740     }
741
742     /**
743      * This method is used by Cordova for hacking.
744      * TODO(yongsheng): remove this and related test cases?
745      *
746      * @hide
747      */
748     @XWalkAPI
749     public void setNetworkAvailable(boolean networkUp) {
750         if (mContent == null) return;
751         checkThreadSafety();
752         mContent.setNetworkAvailable(networkUp);
753     }
754
755     /**
756      * Enables remote debugging and returns the URL at which the dev tools server is listening
757      * for commands. The allowedUid argument can be used to specify the uid of the process that is
758      * permitted to connect.
759      * TODO(yongsheng): how to enable this in XWalkPreferencesInternal?
760      *
761      * @hide
762      */
763     public String enableRemoteDebugging(int allowedUid) {
764         if (mContent == null) return null;
765         checkThreadSafety();
766         return mContent.enableRemoteDebugging(allowedUid);
767     }
768
769     /**
770      * It's used for Presentation API.
771      * @hide
772      */
773     public int getContentID() {
774         if (mContent == null) return -1;
775         return mContent.getRoutingID();
776     }
777
778     boolean canGoBack() {
779         if (mContent == null) return false;
780         checkThreadSafety();
781         return mContent.canGoBack();
782     }
783
784     void goBack() {
785         if (mContent == null) return;
786         checkThreadSafety();
787         mContent.goBack();
788     }
789
790     boolean canGoForward() {
791         if (mContent == null) return false;
792         checkThreadSafety();
793         return mContent.canGoForward();
794     }
795
796     void goForward() {
797         if (mContent == null) return;
798         checkThreadSafety();
799         mContent.goForward();
800     }
801
802     void clearHistory() {
803         if (mContent == null) return;
804         checkThreadSafety();
805         mContent.clearHistory();
806     }
807
808     void destroy() {
809         if (mContent == null) return;
810         ApplicationStatus.unregisterActivityStateListener(mActivityStateListener);
811         mActivityStateListener = null;
812         if (null != mExtensionManager) mExtensionManager.onDestroy();
813         mContent.destroy();
814         disableRemoteDebugging();
815     }
816
817     // Enables remote debugging and returns the URL at which the dev tools server is listening
818     // for commands. Only the current process is allowed to connect to the server.
819     String enableRemoteDebugging() {
820         return enableRemoteDebugging(mContext.getApplicationInfo().uid);
821     }
822
823     void disableRemoteDebugging() {
824         if (mContent == null) return;
825         checkThreadSafety();
826         mContent.disableRemoteDebugging();
827     }
828
829     private static void checkThreadSafety() {
830         if (Looper.myLooper() != Looper.getMainLooper()) {
831             Throwable throwable = new Throwable(
832                 "Warning: A XWalkViewInternal method was called on thread '" +
833                 Thread.currentThread().getName() + "'. " +
834                 "All XWalkViewInternal methods must be called on the UI thread. ");
835             throw new RuntimeException(throwable);
836         }
837     }
838
839     boolean isOwnerActivityRunning() {
840         int status = ApplicationStatus.getStateForActivity(getActivity());
841         if (status == ActivityState.DESTROYED) return false;
842         return true;
843     }
844
845     void navigateTo(int offset) {
846         if (mContent == null) return;
847         mContent.navigateTo(offset);
848     }
849
850     void setOverlayVideoMode(boolean enabled) {
851         mContent.setOverlayVideoMode(enabled);
852     }
853
854     // Below methods are for test shell and instrumentation tests.
855     /**
856      * @hide
857      */
858     public void setXWalkClient(XWalkClient client) {
859         if (mContent == null) return;
860         checkThreadSafety();
861         mContent.setXWalkClient(client);
862     }
863
864     /**
865      * @hide
866      */
867     public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
868         if (mContent == null) return;
869         checkThreadSafety();
870         mContent.setXWalkWebChromeClient(client);
871     }
872
873     /**
874      * @hide
875      */
876     public void setDownloadListener(DownloadListener listener) {
877         if (mContent == null) return;
878         checkThreadSafety();
879         mContent.setDownloadListener(listener);
880     }
881
882     /**
883      * @hide
884      */
885     public void setNavigationHandler(XWalkNavigationHandler handler) {
886         if (mContent == null) return;
887         checkThreadSafety();
888         mContent.setNavigationHandler(handler);
889     }
890
891     /**
892      * @hide
893      */
894     public void setNotificationService(XWalkNotificationService service) {
895         if (mContent == null) return;
896         checkThreadSafety();
897         mContent.setNotificationService(service);
898     }
899
900     /**
901      * @hide
902      */
903     @Override
904     public boolean dispatchKeyEvent(KeyEvent event) {
905         if (event.getAction() == KeyEvent.ACTION_UP &&
906                 event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
907             // If there's navigation happens when app is fullscreen,
908             // the content will still be fullscreen after navigation.
909             // In such case, the back key will exit fullscreen first.
910             if (hasEnteredFullscreen()) {
911                 leaveFullscreen();
912                 return true;
913             } else if (canGoBack()) {
914                 goBack();
915                 return true;
916             }
917         }
918         return super.dispatchKeyEvent(event);
919     }
920
921     private void onActivityStateChange(Activity activity, int newState) {
922         assert(getActivity() == activity);
923         switch (newState) {
924             case ActivityState.STARTED:
925                 onShow();
926                 if (null != mExtensionManager) mExtensionManager.onStart();
927                 break;
928             case ActivityState.PAUSED:
929                 pauseTimers();
930                 if (null != mExtensionManager) mExtensionManager.onPause();
931                 break;
932             case ActivityState.RESUMED:
933                 resumeTimers();
934                 if (null != mExtensionManager) mExtensionManager.onResume();
935                 break;
936             case ActivityState.DESTROYED:
937                 onDestroy();
938                 break;
939             case ActivityState.STOPPED:
940                 onHide();
941                 if (null != mExtensionManager) mExtensionManager.onStop();
942                 break;
943             default:
944                 break;
945         }
946     }
947 }