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