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