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