4a0c8d8f8caf235a65d03436d320c2d7c3c6f709
[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     public void completeWindowCreation(XWalkViewInternal newXWalkView) {
254         mContent.supplyContentsForPopup(newXWalkView == null ? null : newXWalkView.mContent);
255     }
256
257     private void init(Context context, AttributeSet attrs) {
258         // Initialize chromium resources. Assign them the correct ids in
259         // xwalk core.
260         XWalkInternalResources.resetIds(context);
261
262         // Intialize library, paks and others.
263         try {
264             XWalkViewDelegate.init(this);
265             mActivityStateListener = new XWalkActivityStateListener(this);
266             ApplicationStatus.registerStateListenerForActivity(
267                     mActivityStateListener, getActivity());
268         } catch (Throwable e) {
269             // Try to find if there is UnsatisfiedLinkError in the cause chain of the met Throwable.
270             Throwable linkError = e;
271             while (true) {
272                 if (linkError == null) throw new RuntimeException(e);
273                 if (linkError instanceof UnsatisfiedLinkError) break;
274                 if (linkError.getCause() == null ||
275                         linkError.getCause().equals(linkError)) {
276                     throw new RuntimeException(e);
277                 }
278                 linkError = linkError.getCause();
279             }
280             final UnsatisfiedLinkError err = (UnsatisfiedLinkError) linkError;
281             final Activity activity = getActivity();
282             final String packageName = context.getPackageName();
283             String missingArch = XWalkViewDelegate.isRunningOnIA() ? "Intel" : "ARM";
284             final String message =
285                     context.getString(R.string.cpu_arch_mismatch_message, missingArch);
286
287             AlertDialog.Builder builder = new AlertDialog.Builder(activity);
288             builder.setTitle(R.string.cpu_arch_mismatch_title)
289                     .setMessage(message)
290                     .setOnCancelListener(new DialogInterface.OnCancelListener() {
291                         @Override
292                         public void onCancel(DialogInterface dialog) {
293                             activity.finish();
294                         }
295                     }).setPositiveButton(R.string.goto_store_button_label,
296                             new DialogInterface.OnClickListener() {
297                         @Override
298                         public void onClick(DialogInterface dialog, int which) {
299                             activity.startActivity(new Intent(Intent.ACTION_VIEW,
300                                     Uri.parse(PLAYSTORE_DETAIL_URI + packageName)));
301                             activity.finish();
302                         }
303                     }).setNeutralButton(R.string.report_feedback_button_label,
304                             new DialogInterface.OnClickListener() {
305                         @Override
306                         public void onClick(DialogInterface dialog, int which) {
307                             ApplicationErrorReport report = new ApplicationErrorReport();
308                             report.type = ApplicationErrorReport.TYPE_CRASH;
309                             report.packageName = report.processName = packageName;
310
311                             ApplicationErrorReport.CrashInfo crash =
312                                     new ApplicationErrorReport.CrashInfo();
313                             crash.exceptionClassName = err.getClass().getSimpleName();
314                             crash.exceptionMessage = "CPU architecture mismatch";
315                             StringWriter writer = new StringWriter();
316                             PrintWriter print = new PrintWriter(writer);
317                             err.printStackTrace(print);
318                             crash.stackTrace = writer.toString();
319                             StackTraceElement stack = err.getStackTrace()[0];
320                             crash.throwClassName = stack.getClassName();
321                             crash.throwFileName = stack.getFileName();
322                             crash.throwLineNumber = stack.getLineNumber();
323                             crash.throwMethodName = stack.getMethodName();
324
325                             report.crashInfo = crash;
326                             report.systemApp = false;
327                             report.time = System.currentTimeMillis();
328
329                             Intent intent = new Intent(Intent.ACTION_APP_ERROR);
330                             intent.putExtra(Intent.EXTRA_BUG_REPORT, report);
331                             activity.startActivity(intent);
332                             activity.finish();
333                         }
334                     });
335             builder.create().show();
336             return;
337         }
338
339         initXWalkContent(context, attrs);
340     }
341
342     private void initXWalkContent(Context context, AttributeSet attrs) {
343         mIsHidden = false;
344         mContent = new XWalkContent(context, attrs, this);
345         addView(mContent,
346                 new FrameLayout.LayoutParams(
347                         FrameLayout.LayoutParams.MATCH_PARENT,
348                         FrameLayout.LayoutParams.MATCH_PARENT));
349
350
351         // Set default XWalkClientImpl.
352         setXWalkClient(new XWalkClient(this));
353         // Set default XWalkWebChromeClient and DownloadListener. The default actions
354         // are provided via the following clients if special actions are not needed.
355         setXWalkWebChromeClient(new XWalkWebChromeClient(this));
356
357         // Set with internal implementation. Could be overwritten by embedders'
358         // setting.
359         setUIClient(new XWalkUIClientInternal(this));
360         setResourceClient(new XWalkResourceClientInternal(this));
361
362         setDownloadListener(new XWalkDownloadListenerImpl(context));
363         setNavigationHandler(new XWalkNavigationHandlerImpl(context));
364         setNotificationService(new XWalkNotificationServiceImpl(context, this));
365
366         if (!CommandLine.getInstance().hasSwitch("disable-xwalk-extensions")) {
367             BuiltinXWalkExtensions.load(context, getActivity());
368         } else {
369             XWalkPreferencesInternal.setValue(XWalkPreferencesInternal.ENABLE_EXTENSIONS, false);
370         }
371
372         XWalkPathHelper.initialize();
373         XWalkPathHelper.setCacheDirectory(
374                 mContext.getApplicationContext().getCacheDir().getPath());
375
376         String state = Environment.getExternalStorageState();
377         if (Environment.MEDIA_MOUNTED.equals(state) ||
378                 Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
379             File extCacheDir =  mContext.getApplicationContext().getExternalCacheDir();
380             if (null != extCacheDir) {
381                 XWalkPathHelper.setExternalCacheDirectory(extCacheDir.getPath());
382             }
383         }
384     }
385
386     /**
387      * Load a web page/app from a given base URL or a content.
388      * If url is null or empty and content is null or empty, then this function
389      * will do nothing.
390      * If content is not null, load the web page/app from the content.
391      * If content is not null and the url is not set, return "about:blank" ifi
392      * calling {@link XWalkViewInternal#getUrl()}.
393      * If content is null, try to load the content from the url.
394      *
395      * It supports URL schemes like 'http:', 'https:' and 'file:'.
396      * It can also load files from Android assets, e.g. 'file:///android_asset/'.
397      * @param url the url for web page/app.
398      * @param content the content for the web page/app. Could be empty.
399      * @since 1.0
400      */
401     @XWalkAPI
402     public void load(String url, String content) {
403         if (mContent == null) return;
404         checkThreadSafety();
405         mContent.loadUrl(url, content);
406     }
407
408     /**
409      * Load a web app from a given manifest.json file. If content is not null,
410      * load the manifest.json from the content. If content is null, try to load
411      * the manifest.json from the url. Note that url should not be null if the
412      * launched path defined in manifest.json is relative.
413      *
414      * It supports URL schemes like 'http:', 'https:' and 'file:'.
415      * It can also load files from Android assets, e.g. 'file:///android_asset/'.
416      * @param url the url for manifest.json.
417      * @param content the content for manifest.json.
418      * @since 1.0
419      */
420     @XWalkAPI
421     public void loadAppFromManifest(String url, String content) {
422         if (mContent == null) return;
423         checkThreadSafety();
424         mContent.loadAppFromManifest(url, content);
425     }
426
427     /**
428      * Reload a web app with a given mode.
429      * @param mode the reload mode.
430      * @since 1.0
431      */
432     @XWalkAPI
433     public void reload(int mode) {
434         if (mContent == null) return;
435         checkThreadSafety();
436         mContent.reload(mode);
437     }
438
439     /**
440      * Stop current loading progress.
441      * @since 1.0
442      */
443     @XWalkAPI
444     public void stopLoading() {
445         if (mContent == null) return;
446         checkThreadSafety();
447         mContent.stopLoading();
448     }
449
450     /**
451      * Get the url of current web page/app. This may be different from what's passed
452      * by caller.
453      * @return the url for current web page/app.
454      * @since 1.0
455      */
456     @XWalkAPI
457     public String getUrl() {
458         if (mContent == null) return null;
459         checkThreadSafety();
460         return mContent.getUrl();
461     }
462
463     /**
464      * Get the title of current web page/app. This may be different from what's passed
465      * by caller.
466      * @return the title for current web page/app.
467      * @since 1.0
468      */
469     @XWalkAPI
470     public String getTitle() {
471         if (mContent == null) return null;
472         checkThreadSafety();
473         return mContent.getTitle();
474     }
475
476     /**
477      * Get the original url specified by caller.
478      * @return the original url.
479      * @since 1.0
480      */
481     @XWalkAPI
482     public String getOriginalUrl() {
483         if (mContent == null) return null;
484         checkThreadSafety();
485         return mContent.getOriginalUrl();
486     }
487
488     /**
489      * Get the navigation history for current XWalkViewInternal. It's synchronized with
490      * this XWalkViewInternal if any backward/forward and navigation operations.
491      * @return the navigation history.
492      * @since 1.0
493      */
494     @XWalkAPI
495     public XWalkNavigationHistoryInternal getNavigationHistory() {
496         if (mContent == null) return null;
497         checkThreadSafety();
498         return mContent.getNavigationHistory();
499     }
500
501     /**
502      * Injects the supplied Java object into this XWalkViewInternal.
503      * Each method defined in the class of the object should be
504      * marked with {@link JavascriptInterface} if it's called by JavaScript.
505      * @param object the supplied Java object, called by JavaScript.
506      * @param name the name injected in JavaScript.
507      * @since 1.0
508      */
509     @XWalkAPI
510     public void addJavascriptInterface(Object object, String name) {
511         if (mContent == null) return;
512         checkThreadSafety();
513         mContent.addJavascriptInterface(object, name);
514     }
515
516     /**
517      * Evaluate a fragment of JavaScript code and get the result via callback.
518      * @param script the JavaScript string.
519      * @param callback the callback to handle the evaluated result.
520      * @since 1.0
521      */
522     @XWalkAPI
523     public void evaluateJavascript(String script, ValueCallback<String> callback) {
524         if (mContent == null) return;
525         checkThreadSafety();
526         mContent.evaluateJavascript(script, callback);
527     }
528
529     /**
530      * Clear the resource cache. Note that the cache is per-application, so this
531      * will clear the cache for all XWalkViews used.
532      * @param includeDiskFiles indicate whether to clear disk files for cache.
533      * @since 1.0
534      */
535     @XWalkAPI
536     public void clearCache(boolean includeDiskFiles) {
537         if (mContent == null) return;
538         checkThreadSafety();
539         mContent.clearCache(includeDiskFiles);
540     }
541
542     /**
543      * Indicate that a HTML element is occupying the whole screen.
544      * @return true if any HTML element is occupying the whole screen.
545      * @since 1.0
546      */
547     @XWalkAPI
548     public boolean hasEnteredFullscreen() {
549         if (mContent == null) return false;
550         checkThreadSafety();
551         return mContent.hasEnteredFullscreen();
552     }
553
554     /**
555      * Leave fullscreen mode if it's. Do nothing if it's not
556      * in fullscreen.
557      * @since 1.0
558      */
559     @XWalkAPI
560     public void leaveFullscreen() {
561         if (mContent == null) return;
562         checkThreadSafety();
563         mContent.exitFullscreen();
564     }
565
566     /**
567      * Pause all layout, parsing and JavaScript timers for all XWalkViewInternal instances.
568      * It will be called when the container Activity get paused. It can also be explicitly
569      * called to pause timers.
570      *
571      * Note that it will globally impact all XWalkViewInternal instances, not limited to
572      * just this XWalkViewInternal.
573      *
574      * @since 1.0
575      */
576     @XWalkAPI
577     public void pauseTimers() {
578         if (mContent == null) return;
579         checkThreadSafety();
580         mContent.pauseTimers();
581     }
582
583     /**
584      * Resume all layout, parsing and JavaScript timers for all XWalkViewInternal instances.
585      * It will be called when the container Activity get resumed. It can also be explicitly
586      * called to resume timers.
587      *
588      * Note that it will globally impact all XWalkViewInternal instances, not limited to
589      * just this XWalkViewInternal.
590      *
591      * @since 1.0
592      */
593     @XWalkAPI
594     public void resumeTimers() {
595         if (mContent == null) return;
596         checkThreadSafety();
597         mContent.resumeTimers();
598     }
599
600     /**
601      * Pause many other things except JavaScript timers inside rendering engine,
602      * like video player, modal dialogs, etc. See {@link #pauseTimers} about pausing
603      * JavaScript timers.
604      * It will be called when the container Activity get paused. It can also be explicitly
605      * called to pause above things.
606      * @since 1.0
607      */
608     @XWalkAPI
609     public void onHide() {
610         if (mContent == null || mIsHidden) return;
611         mContent.onPause();
612         mIsHidden = true;
613     }
614
615     /**
616      * Resume video player, modal dialogs. Embedders are in charge of calling
617      * this during resuming this activity if they call onHide.
618      * Typically it should be called when the activity for this view is resumed.
619      * It will be called when the container Activity get resumed. It can also be explicitly
620      * called to resume above things.
621      * @since 1.0
622      */
623     @XWalkAPI
624     public void onShow() {
625         if (mContent == null || !mIsHidden ) return;
626         mContent.onResume();
627         mIsHidden = false;
628     }
629
630     /**
631      * Release internal resources occupied by this XWalkViewInternal.
632      * It will be called when the container Activity get destroyed. It can also be explicitly
633      * called to release resources.
634      * @since 1.0
635      */
636     @XWalkAPI
637     public void onDestroy() {
638         destroy();
639     }
640
641     /**
642      * Pass through activity result to XWalkViewInternal. Many internal facilities need this
643      * to handle activity result like JavaScript dialog, Crosswalk extensions, etc.
644      * See <a href="http://developer.android.com/reference/android/app/Activity.html">
645      * android.app.Activity.onActivityResult()</a>.
646      * @param requestCode passed from android.app.Activity.onActivityResult().
647      * @param resultCode passed from android.app.Activity.onActivityResult().
648      * @param data passed from android.app.Activity.onActivityResult().
649      * @since 1.0
650      */
651     @XWalkAPI
652     public void onActivityResult(int requestCode, int resultCode, Intent data) {
653         if (mContent == null) return;
654         mContent.onActivityResult(requestCode, resultCode, data);
655     }
656
657     /**
658      * Pass through intents to XWalkViewInternal. Many internal facilities need this
659      * to receive the intents like web notification. See
660      * <a href="http://developer.android.com/reference/android/app/Activity.html">
661      * android.app.Activity.onNewIntent()</a>.
662      * @param intent passed from android.app.Activity.onNewIntent().
663      * @since 1.0
664      */
665     @XWalkAPI
666     public boolean onNewIntent(Intent intent) {
667         if (mContent == null) return false;
668         return mContent.onNewIntent(intent);
669     }
670
671     /**
672      * Save current internal state of this XWalkViewInternal. This can help restore this state
673      * afterwards restoring.
674      * @param outState the saved state for restoring.
675      * @since 1.0
676      */
677     @XWalkAPI
678     public boolean saveState(Bundle outState) {
679         if (mContent == null) return false;
680         mContent.saveState(outState);
681         return true;
682     }
683
684     /**
685      * Restore the state from the saved bundle data.
686      * @param inState the state saved from saveState().
687      * @return true if it can restore the state.
688      * @since 1.0
689      */
690     @XWalkAPI
691     public boolean restoreState(Bundle inState) {
692         if (mContent == null) return false;
693         if (mContent.restoreState(inState) != null) return true;
694         return false;
695     }
696
697     /**
698      * Get the API version of Crosswalk embedding API.
699      * @return the string of API level.
700      * @since 1.0
701      */
702     // TODO(yongsheng): make it static?
703     @XWalkAPI
704     public String getAPIVersion() {
705         return "3.0";
706     }
707
708     /**
709      * Get the Crosswalk version.
710      * @return the string of Crosswalk.
711      * @since 1.0
712      */
713     // TODO(yongsheng): make it static?
714     @XWalkAPI
715     public String getXWalkVersion() {
716         if (mContent == null) return null;
717         return mContent.getXWalkVersion();
718     }
719
720     /**
721      * Embedders use this to customize their handlers to events/callbacks related
722      * to UI.
723      * @param client the XWalkUIClientInternal defined by callers.
724      * @since 1.0
725      */
726     @XWalkAPI
727     public void setUIClient(XWalkUIClientInternal client) {
728         if (mContent == null) return;
729         checkThreadSafety();
730         mContent.setUIClient(client);
731     }
732
733     /**
734      * Embedders use this to customize their handlers to events/callbacks related
735      * to resource loading.
736      * @param client the XWalkResourceClientInternal defined by callers.
737      * @since 1.0
738      */
739     @XWalkAPI
740     public void setResourceClient(XWalkResourceClientInternal client) {
741         if (mContent == null) return;
742         checkThreadSafety();
743         mContent.setResourceClient(client);
744     }
745
746     // TODO(yongsheng): this is not public.
747     /**
748      * @hide
749      */
750     public XWalkSettings getSettings() {
751         if (mContent == null) return null;
752         checkThreadSafety();
753         return mContent.getSettings();
754     }
755
756     /**
757      * This method is used by Cordova for hacking.
758      * TODO(yongsheng): remove this and related test cases?
759      *
760      * @hide
761      */
762     @XWalkAPI
763     public void setNetworkAvailable(boolean networkUp) {
764         if (mContent == null) return;
765         checkThreadSafety();
766         mContent.setNetworkAvailable(networkUp);
767     }
768
769     /**
770      * Enables remote debugging and returns the URL at which the dev tools
771      * server is listening for commands.
772      * The allowedUid argument can be used to specify the uid of the process
773      * that is permitted to connect.
774      * TODO(wang16): Hide or remove this API after new API for getting remote
775      *               debugging url available.
776      *
777      * @hide
778      */
779     @XWalkAPI
780     public String enableRemoteDebugging(int allowedUid) {
781         if (mContent == null) return null;
782         checkThreadSafety();
783         return mContent.enableRemoteDebugging(allowedUid);
784     }
785
786     /**
787      * It's used for Presentation API.
788      * @hide
789      */
790     public int getContentID() {
791         if (mContent == null) return -1;
792         return mContent.getRoutingID();
793     }
794
795     boolean canGoBack() {
796         if (mContent == null) return false;
797         checkThreadSafety();
798         return mContent.canGoBack();
799     }
800
801     void goBack() {
802         if (mContent == null) return;
803         checkThreadSafety();
804         mContent.goBack();
805     }
806
807     boolean canGoForward() {
808         if (mContent == null) return false;
809         checkThreadSafety();
810         return mContent.canGoForward();
811     }
812
813     void goForward() {
814         if (mContent == null) return;
815         checkThreadSafety();
816         mContent.goForward();
817     }
818
819     void clearHistory() {
820         if (mContent == null) return;
821         checkThreadSafety();
822         mContent.clearHistory();
823     }
824
825     void destroy() {
826         if (mContent == null) return;
827         ApplicationStatus.unregisterActivityStateListener(mActivityStateListener);
828         mActivityStateListener = null;
829         mContent.destroy();
830         disableRemoteDebugging();
831     }
832
833     // Enables remote debugging and returns the URL at which the dev tools server is listening
834     // for commands. Only the current process is allowed to connect to the server.
835     String enableRemoteDebugging() {
836         return enableRemoteDebugging(mContext.getApplicationInfo().uid);
837     }
838
839     void disableRemoteDebugging() {
840         if (mContent == null) return;
841         checkThreadSafety();
842         mContent.disableRemoteDebugging();
843     }
844
845     private static void checkThreadSafety() {
846         if (Looper.myLooper() != Looper.getMainLooper()) {
847             Throwable throwable = new Throwable(
848                 "Warning: A XWalkViewInternal method was called on thread '" +
849                 Thread.currentThread().getName() + "'. " +
850                 "All XWalkViewInternal methods must be called on the UI thread. ");
851             throw new RuntimeException(throwable);
852         }
853     }
854
855     boolean isOwnerActivityRunning() {
856         int status = ApplicationStatus.getStateForActivity(getActivity());
857         if (status == ActivityState.DESTROYED) return false;
858         return true;
859     }
860
861     void navigateTo(int offset) {
862         if (mContent == null) return;
863         mContent.navigateTo(offset);
864     }
865
866     void setOverlayVideoMode(boolean enabled) {
867         mContent.setOverlayVideoMode(enabled);
868     }
869
870     // Below methods are for test shell and instrumentation tests.
871     /**
872      * @hide
873      */
874     public void setXWalkClient(XWalkClient client) {
875         if (mContent == null) return;
876         checkThreadSafety();
877         mContent.setXWalkClient(client);
878     }
879
880     /**
881      * @hide
882      */
883     public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
884         if (mContent == null) return;
885         checkThreadSafety();
886         mContent.setXWalkWebChromeClient(client);
887     }
888
889     /**
890      * @hide
891      */
892     public void setDownloadListener(DownloadListener listener) {
893         if (mContent == null) return;
894         checkThreadSafety();
895         mContent.setDownloadListener(listener);
896     }
897
898     /**
899      * @hide
900      */
901     public void setNavigationHandler(XWalkNavigationHandler handler) {
902         if (mContent == null) return;
903         checkThreadSafety();
904         mContent.setNavigationHandler(handler);
905     }
906
907     /**
908      * @hide
909      */
910     public void setNotificationService(XWalkNotificationService service) {
911         if (mContent == null) return;
912         checkThreadSafety();
913         mContent.setNotificationService(service);
914     }
915
916     /**
917      * @hide
918      */
919     @Override
920     public boolean dispatchKeyEvent(KeyEvent event) {
921         if (event.getAction() == KeyEvent.ACTION_UP &&
922                 event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
923             // If there's navigation happens when app is fullscreen,
924             // the content will still be fullscreen after navigation.
925             // In such case, the back key will exit fullscreen first.
926             if (hasEnteredFullscreen()) {
927                 leaveFullscreen();
928                 return true;
929             } else if (canGoBack()) {
930                 goBack();
931                 return true;
932             }
933         }
934         return super.dispatchKeyEvent(event);
935     }
936
937     private void onActivityStateChange(Activity activity, int newState) {
938         assert(getActivity() == activity);
939         switch (newState) {
940             case ActivityState.STARTED:
941                 onShow();
942                 break;
943             case ActivityState.PAUSED:
944                 pauseTimers();
945                 break;
946             case ActivityState.RESUMED:
947                 resumeTimers();
948                 break;
949             case ActivityState.DESTROYED:
950                 onDestroy();
951                 break;
952             case ActivityState.STOPPED:
953                 onHide();
954                 break;
955             default:
956                 break;
957         }
958     }
959 }