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