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