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