Upstream version 6.35.132.0
[platform/framework/web/crosswalk.git] / src / xwalk / runtime / android / core / src / org / xwalk / core / XWalkView.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;
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.Looper;
17 import android.util.AttributeSet;
18 import android.view.KeyEvent;
19 import android.view.ViewGroup;
20 import android.webkit.ValueCallback;
21 import android.widget.FrameLayout;
22
23 import java.io.PrintWriter;
24 import java.io.StringWriter;
25
26 import org.chromium.base.ActivityState;
27 import org.chromium.base.ApplicationStatus;
28
29 import org.xwalk.core.extension.XWalkExtensionManager;
30
31 /**
32  * XWalkView represents an Android view for web apps/pages. Thus most of attributes
33  * for Android view are true for this class. It includes an instance of
34  * android.view.SurfaceView for rendering. Currently limitations for android.view.SurfaceView
35  * also are applied for this class as well, like resizing, retation, transformation and
36  * animation.
37  *
38  * It provides 2 major callback classes, namely XWalkResourceClient and XWalkUIClient for
39  * listening to the events related resource loading and UI. By default, Crosswalk has an inner
40  * implementation. Callers can override them if like.
41  *
42  * Unlike other Android views, this class has to listen to system events like application life
43  * cycle, intents, and activity result. The web engine inside this view need to handle them.
44  *
45  * It already includes all newly created Web APIs from Crosswalk like Presentation,
46  * DeviceCapabilities, etc..
47  */
48 public class XWalkView extends android.widget.FrameLayout {
49
50     protected static final String PLAYSTORE_DETAIL_URI = "market://details?id=";
51
52     private XWalkContent mContent;
53     private Activity mActivity;
54     private Context mContext;
55     private XWalkExtensionManager mExtensionManager;
56     private boolean mIsTimerPaused;
57     private boolean mIsHidden;
58
59     /** Normal reload mode as default. */
60     public static final int RELOAD_NORMAL = 0;
61     /** Reload mode with bypassing the cache. */
62     public static final int RELOAD_IGNORE_CACHE = 1;
63
64     /**
65      * Constructor for inflating via XML.
66      * @param context  a Context object used to access application assets.
67      * @param attrs    an AttributeSet passed to our parent.
68      */
69     public XWalkView(Context context, AttributeSet attrs) {
70         super(context, attrs);
71
72         checkThreadSafety();
73         mContext = context;
74         init(context, attrs);
75     }
76
77     /**
78      * Constructor for Crosswalk runtime. In shared mode, context isi
79      * different from activity. In embedded mode, they're same.
80      * @param context  a Context object used to access application assets
81      * @param activity the activity for this XWalkView.
82      */
83     public XWalkView(Context context, Activity activity) {
84         super(context, null);
85         checkThreadSafety();
86
87         // Make sure mActivity is initialized before calling 'init' method.
88         mActivity = activity;
89         mContext = context;
90         init(context, null);
91     }
92
93     /**
94      * Get the current activity passed from callers. It's never null.
95      * @return the activity instance passed from callers.
96      *
97      * @hide
98      */
99     public Activity getActivity() {
100         if (mActivity != null) {
101             return mActivity;
102         } else if (getContext() instanceof Activity) {
103             return (Activity)getContext();
104         }
105
106         // Never achieve here.
107         assert(false);
108         return null;
109     }
110
111     // TODO(yongsheng): we should remove this since we have getContext()?
112     /**
113      * @hide
114      */
115     public Context getViewContext() {
116         return mContext;
117     }
118
119     private void init(Context context, AttributeSet attrs) {
120         // Initialize chromium resources. Assign them the correct ids in
121         // xwalk core.
122         XWalkInternalResources.resetIds(context);
123
124         // Intialize library, paks and others.
125         try {
126             XWalkViewDelegate.init(this);
127         } catch (UnsatisfiedLinkError e) {
128             final UnsatisfiedLinkError err = e;
129             final Activity activity = getActivity();
130             final String packageName = context.getPackageName();
131             String missingArch = XWalkViewDelegate.isRunningOnIA() ? "Intel" : "ARM";
132             final String message =
133                     context.getString(R.string.cpu_arch_mismatch_message, missingArch);
134
135             AlertDialog.Builder builder = new AlertDialog.Builder(activity);
136             builder.setTitle(R.string.cpu_arch_mismatch_title)
137                     .setMessage(message)
138                     .setOnCancelListener(new DialogInterface.OnCancelListener() {
139                         @Override
140                         public void onCancel(DialogInterface dialog) {
141                             activity.finish();
142                         }
143                     }).setPositiveButton(R.string.goto_store_button_label,
144                             new DialogInterface.OnClickListener() {
145                         @Override
146                         public void onClick(DialogInterface dialog, int which) {
147                             activity.startActivity(new Intent(Intent.ACTION_VIEW,
148                                     Uri.parse(PLAYSTORE_DETAIL_URI + packageName)));
149                             activity.finish();
150                         }
151                     }).setNeutralButton(R.string.report_feedback_button_label,
152                             new DialogInterface.OnClickListener() {
153                         @Override
154                         public void onClick(DialogInterface dialog, int which) {
155                             ApplicationErrorReport report = new ApplicationErrorReport();
156                             report.type = ApplicationErrorReport.TYPE_CRASH;
157                             report.packageName = report.processName = packageName;
158
159                             ApplicationErrorReport.CrashInfo crash =
160                                     new ApplicationErrorReport.CrashInfo();
161                             crash.exceptionClassName = err.getClass().getSimpleName();
162                             crash.exceptionMessage = "CPU architecture mismatch";
163                             StringWriter writer = new StringWriter();
164                             PrintWriter print = new PrintWriter(writer);
165                             err.printStackTrace(print);
166                             crash.stackTrace = writer.toString();
167                             StackTraceElement stack = err.getStackTrace()[0];
168                             crash.throwClassName = stack.getClassName();
169                             crash.throwFileName = stack.getFileName();
170                             crash.throwLineNumber = stack.getLineNumber();
171                             crash.throwMethodName = stack.getMethodName();
172
173                             report.crashInfo = crash;
174                             report.systemApp = false;
175                             report.time = System.currentTimeMillis();
176
177                             Intent intent = new Intent(Intent.ACTION_APP_ERROR);
178                             intent.putExtra(Intent.EXTRA_BUG_REPORT, report);
179                             activity.startActivity(intent);
180                             activity.finish();
181                         }
182                     });
183             builder.create().show();
184             return;
185         }
186
187         initXWalkContent(context, attrs);
188     }
189
190     private void initXWalkContent(Context context, AttributeSet attrs) {
191         mIsTimerPaused = false;
192         mIsHidden = false;
193         mContent = new XWalkContent(context, attrs, this);
194         addView(mContent,
195                 new FrameLayout.LayoutParams(
196                         FrameLayout.LayoutParams.MATCH_PARENT,
197                         FrameLayout.LayoutParams.MATCH_PARENT));
198
199
200         // Set default XWalkClientImpl.
201         setXWalkClient(new XWalkClient(this));
202         // Set default XWalkWebChromeClient and DownloadListener. The default actions
203         // are provided via the following clients if special actions are not needed.
204         setXWalkWebChromeClient(new XWalkWebChromeClient(this));
205
206         // Set with internal implementation. Could be overwritten by embedders'
207         // setting.
208         setUIClient(new XWalkUIClient(this));
209         setResourceClient(new XWalkResourceClient(this));
210
211         setDownloadListener(new XWalkDownloadListenerImpl(context));
212         setNavigationHandler(new XWalkNavigationHandlerImpl(context));
213         setNotificationService(new XWalkNotificationServiceImpl(context, this));
214
215         // Enable xwalk extension mechanism and start load extensions here.
216         // Note that it has to be after above initialization.
217         mExtensionManager = new XWalkExtensionManager(context, getActivity());
218         mExtensionManager.loadExtensions();
219     }
220
221     /**
222      * Load a web page/app from a given base URL or a content.
223      * If url is null or empty and content is null or empty, then this function
224      * will do nothing.
225      * If content is not null, load the web page/app from the content.
226      * If content is not null and the url is not set, return "about:blank" ifi
227      * calling {@link XWalkView#getUrl()}.
228      * If content is null, try to load the content from the url.
229      *
230      * It supports URL schemes like 'http:', 'https:' and 'file:'.
231      * It can also load files from Android assets, e.g. 'file:///android_asset/'.
232      * @param url the url for web page/app.
233      * @param content the content for the web page/app. Could be empty.
234      */
235     public void load(String url, String content) {
236         if (mContent == null) return;
237         checkThreadSafety();
238         mContent.loadUrl(url, content);
239     }
240
241     /**
242      * Load a web app from a given manifest.json file. If content is not null,
243      * load the manifest.json from the content. If content is null, try to load
244      * the manifest.json from the url. Note that url should not be null if the
245      * launched path defined in manifest.json is relative.
246      *
247      * It supports URL schemes like 'http:', 'https:' and 'file:'.
248      * It can also load files from Android assets, e.g. 'file:///android_asset/'.
249      * @param url the url for manifest.json.
250      * @param content the content for manifest.json.
251      */
252     public void loadAppFromManifest(String url, String content) {
253         if (mContent == null) return;
254         checkThreadSafety();
255         mContent.loadAppFromManifest(url, content);
256     }
257
258     /**
259      * Reload a web app with a given mode.
260      * @param mode the reload mode.
261      */
262     public void reload(int mode) {
263         if (mContent == null) return;
264         checkThreadSafety();
265         mContent.reload(mode);
266     }
267
268     /**
269      * Stop current loading progress.
270      */
271     public void stopLoading() {
272         if (mContent == null) return;
273         checkThreadSafety();
274         mContent.stopLoading();
275     }
276
277     /**
278      * Get the url of current web page/app. This may be different from what's passed
279      * by caller.
280      * @return the url for current web page/app.
281      */
282     public String getUrl() {
283         if (mContent == null) return null;
284         checkThreadSafety();
285         return mContent.getUrl();
286     }
287
288     /**
289      * Get the title of current web page/app. This may be different from what's passed
290      * by caller.
291      * @return the title for current web page/app.
292      */
293     public String getTitle() {
294         if (mContent == null) return null;
295         checkThreadSafety();
296         return mContent.getTitle();
297     }
298
299     /**
300      * Get the original url specified by caller.
301      * @return the original url.
302      */
303     public String getOriginalUrl() {
304         if (mContent == null) return null;
305         checkThreadSafety();
306         return mContent.getOriginalUrl();
307     }
308
309     /**
310      * Get the navigation history for current XWalkView. It's synchronized with
311      * this XWalkView if any backward/forward and navigation operations.
312      * @return the navigation history.
313      */
314     public XWalkNavigationHistory getNavigationHistory() {
315         if (mContent == null) return null;
316         checkThreadSafety();
317         return mContent.getNavigationHistory();
318     }
319
320     /**
321      * Injects the supplied Java object into this XWalkView.
322      * Each method defined in the class of the object should be
323      * marked with {@link JavascriptInterface} if it's called by JavaScript.
324      * @param object the supplied Java object, called by JavaScript.
325      * @param name the name injected in JavaScript.
326      */
327     public void addJavascriptInterface(Object object, String name) {
328         if (mContent == null) return;
329         checkThreadSafety();
330         mContent.addJavascriptInterface(object, name);
331     }
332
333     /**
334      * Evaluate a fragment of JavaScript code and get the result via callback.
335      * @param script the JavaScript string.
336      * @param callback the callback to handle the evaluated result.
337      */
338     public void evaluateJavascript(String script, ValueCallback<String> callback) {
339         if (mContent == null) return;
340         checkThreadSafety();
341         mContent.evaluateJavascript(script, callback);
342     }
343
344     /**
345      * Clear the resource cache. Note that the cache is per-application, so this
346      * will clear the cache for all XWalkViews used.
347      * @param includeDiskFiles indicate whether to clear disk files for cache.
348      */
349     public void clearCache(boolean includeDiskFiles) {
350         if (mContent == null) return;
351         checkThreadSafety();
352         mContent.clearCache(includeDiskFiles);
353     }
354
355     /**
356      * Indicate that a HTML element is occupying the whole screen.
357      * @return true if any HTML element is occupying the whole screen.
358      */
359     public boolean hasEnteredFullscreen() {
360         if (mContent == null) return false;
361         checkThreadSafety();
362         return mContent.hasEnteredFullscreen();
363     }
364
365     /**
366      * Leave fullscreen mode if it's. Do nothing if it's not
367      * in fullscreen.
368      */
369     public void leaveFullscreen() {
370         if (mContent == null) return;
371         checkThreadSafety();
372         mContent.exitFullscreen();
373     }
374
375     /**
376      * Pause all layout, parsing and JavaScript timers for all XWalkView instances.
377      * Typically it should be called when the activity for this view is paused,
378      * and accordingly {@link #resumeTimers} should be called when the activity
379      * is resumed again.
380      *
381      * Note that it will globally impact all XWalkView instances, not limited to
382      * just this XWalkView.
383      */
384     public void pauseTimers() {
385         if (mContent == null || mIsTimerPaused) return;
386         checkThreadSafety();
387         mContent.pauseTimers();
388         mIsTimerPaused = true;
389     }
390
391     /**
392      * Resume all layout, parsing and JavaScript timers for all XWalkView instances.
393      * Typically it should be called when the activity for this view is resumed.
394      *
395      * Note that it will globally impact all XWalkView instances, not limited to
396      * just this XWalkView.
397      */
398     public void resumeTimers() {
399         if (mContent == null || !mIsTimerPaused) return;
400         checkThreadSafety();
401         mContent.resumeTimers();
402         mIsTimerPaused = false;
403     }
404
405     /**
406      * Pause many other things except JavaScript timers inside rendering engine,
407      * like video player, modal dialogs, etc. See {@link #pauseTimers} about pausing
408      * JavaScript timers.
409      * Typically it should be called when the activity for this view is paused.
410      */
411     public void onHide() {
412         if (mContent == null || mIsHidden) return;
413         mExtensionManager.onPause();
414         mContent.onPause();
415         mIsHidden = true;
416     }
417
418     /**
419      * Resume video player, modal dialogs. Embedders are in charge of calling
420      * this during resuming this activity if they call onHide.
421      * Typically it should be called when the activity for this view is resumed.
422      */
423     public void onShow() {
424         if (mContent == null || !mIsHidden ) return;
425         mExtensionManager.onResume();
426         mContent.onResume();
427         mIsHidden = false;
428     }
429
430     /**
431      * Release internal resources occupied by this XWalkView.
432      */
433     public void onDestroy() {
434         destroy();
435     }
436
437     /**
438      * Pass through activity result to XWalkView. Many internal facilities need this
439      * to handle activity result like JavaScript dialog, Crosswalk extensions, etc.
440      * See android.app.Activity.onActivityResult().
441      * @param requestCode passed from android.app.Activity.onActivityResult().
442      * @param resultCode passed from android.app.Activity.onActivityResult().
443      * @param data passed from android.app.Activity.onActivityResult().
444      */
445     public void onActivityResult(int requestCode, int resultCode, Intent data) {
446         if (mContent == null) return;
447         mExtensionManager.onActivityResult(requestCode, resultCode, data);
448         mContent.onActivityResult(requestCode, resultCode, data);
449     }
450
451     /**
452      * Pass through intents to XWalkView. Many internal facilities need this
453      * to receive the intents like web notification. See
454      * android.app.Activity.onNewIntent().
455      * @param intent passed from android.app.Activity.onNewIntent().
456      */
457     public boolean onNewIntent(Intent intent) {
458         if (mContent == null) return false;
459         return mContent.onNewIntent(intent);
460     }
461
462     /**
463      * Save current internal state of this XWalkView. This can help restore this state
464      * afterwards restoring.
465      * @param outState the saved state for restoring.
466      */
467     public boolean saveState(Bundle outState) {
468         if (mContent == null) return false;
469         mContent.saveState(outState);
470         return true;
471     }
472
473     /**
474      * Restore the state from the saved bundle data.
475      * @param inState the state saved from saveState().
476      * @return true if it can restore the state.
477      */
478     public boolean restoreState(Bundle inState) {
479         if (mContent == null) return false;
480         if (mContent.restoreState(inState) != null) return true;
481         return false;
482     }
483
484     /**
485      * Get the API version of Crosswalk embedding API.
486      * @return the string of API level.
487      */
488     // TODO(yongsheng): make it static?
489     public String getAPIVersion() {
490         return "1.0";
491     }
492
493     /**
494      * Get the Crosswalk version.
495      * @return the string of Crosswalk.
496      */
497     // TODO(yongsheng): make it static?
498     public String getXWalkVersion() {
499         if (mContent == null) return null;
500         return mContent.getXWalkVersion();
501     }
502
503     /**
504      * Embedders use this to customize their handlers to events/callbacks related
505      * to UI.
506      * @param client the XWalkUIClient defined by callers.
507      */
508     public void setUIClient(XWalkUIClient client) {
509         if (mContent == null) return;
510         checkThreadSafety();
511         mContent.setUIClient(client);
512     }
513
514     /**
515      * Embedders use this to customize their handlers to events/callbacks related
516      * to resource loading.
517      * @param client the XWalkResourceClient defined by callers.
518      */
519     public void setResourceClient(XWalkResourceClient client) {
520         if (mContent == null) return;
521         checkThreadSafety();
522         mContent.setResourceClient(client);
523     }
524
525     /**
526      * Inherit from android.view.View. This class needs to handle some keys like
527      * 'BACK'.
528      * @param keyCode passed from android.view.View.onKeyUp().
529      * @param event passed from android.view.View.onKeyUp().
530      */
531     @Override
532     public boolean onKeyUp(int keyCode, KeyEvent event) {
533         if (keyCode == KeyEvent.KEYCODE_BACK) {
534             // If there's navigation happens when app is fullscreen,
535             // the content will still be fullscreen after navigation.
536             // In such case, the back key will exit fullscreen first.
537             if (hasEnteredFullscreen()) {
538                 leaveFullscreen();
539                 return true;
540             } else if (canGoBack()) {
541                 goBack();
542                 return true;
543             }
544         }
545         return false;
546     }
547
548     // TODO(yongsheng): this is not public.
549     /**
550      * @hide
551      */
552     public XWalkSettings getSettings() {
553         if (mContent == null) return null;
554         checkThreadSafety();
555         return mContent.getSettings();
556     }
557
558     /**
559      * This method is used by Cordova for hacking.
560      * TODO(yongsheng): remove this and related test cases?
561      *
562      * @hide
563      */
564     public void setNetworkAvailable(boolean networkUp) {
565         if (mContent == null) return;
566         checkThreadSafety();
567         mContent.setNetworkAvailable(networkUp);
568     }
569
570     /**
571      * Enables remote debugging and returns the URL at which the dev tools server is listening
572      * for commands. The allowedUid argument can be used to specify the uid of the process that is
573      * permitted to connect.
574      * TODO(yongsheng): how to enable this in XWalkPreferences?
575      *
576      * @hide
577      */
578     public String enableRemoteDebugging(int allowedUid) {
579         if (mContent == null) return null;
580         checkThreadSafety();
581         return mContent.enableRemoteDebugging(allowedUid);
582     }
583
584     /**
585      * It's used for Presentation API.
586      * @hide
587      */
588     public int getContentID() {
589         if (mContent == null) return -1;
590         return mContent.getRoutingID();
591     }
592
593     boolean canGoBack() {
594         if (mContent == null) return false;
595         checkThreadSafety();
596         return mContent.canGoBack();
597     }
598
599     void goBack() {
600         if (mContent == null) return;
601         checkThreadSafety();
602         mContent.goBack();
603     }
604
605     boolean canGoForward() {
606         if (mContent == null) return false;
607         checkThreadSafety();
608         return mContent.canGoForward();
609     }
610
611     void goForward() {
612         if (mContent == null) return;
613         checkThreadSafety();
614         mContent.goForward();
615     }
616
617     void clearHistory() {
618         if (mContent == null) return;
619         checkThreadSafety();
620         mContent.clearHistory();
621     }
622
623     void destroy() {
624         if (mContent == null) return;
625         mExtensionManager.onDestroy();
626         mContent.destroy();
627         disableRemoteDebugging();
628     }
629
630     // Enables remote debugging and returns the URL at which the dev tools server is listening
631     // for commands. Only the current process is allowed to connect to the server.
632     String enableRemoteDebugging() {
633         return enableRemoteDebugging(mContext.getApplicationInfo().uid);
634     }
635
636     void disableRemoteDebugging() {
637         if (mContent == null) return;
638         checkThreadSafety();
639         mContent.disableRemoteDebugging();
640     }
641
642     private static void checkThreadSafety() {
643         if (Looper.myLooper() != Looper.getMainLooper()) {
644             Throwable throwable = new Throwable(
645                 "Warning: A XWalkView method was called on thread '" +
646                 Thread.currentThread().getName() + "'. " +
647                 "All XWalkView methods must be called on the UI thread. ");
648             throw new RuntimeException(throwable);
649         }
650     }
651
652     boolean isOwnerActivityRunning() {
653         int status = ApplicationStatus.getStateForActivity(getActivity());
654         if (status == ActivityState.DESTROYED) return false;
655         return true;
656     }
657
658     void navigateTo(int offset) {
659         if (mContent == null) return;
660         mContent.navigateTo(offset);
661     }
662
663     void setOverlayVideoMode(boolean enabled) {
664         mContent.setOverlayVideoMode(enabled);
665     }
666
667     // Below methods are for test shell and instrumentation tests.
668     /**
669      * @hide
670      */
671     public void setXWalkClient(XWalkClient client) {
672         if (mContent == null) return;
673         checkThreadSafety();
674         mContent.setXWalkClient(client);
675     }
676
677     /**
678      * @hide
679      */
680     public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
681         if (mContent == null) return;
682         checkThreadSafety();
683         mContent.setXWalkWebChromeClient(client);
684     }
685
686     /**
687      * @hide
688      */
689     public void setDownloadListener(DownloadListener listener) {
690         if (mContent == null) return;
691         checkThreadSafety();
692         mContent.setDownloadListener(listener);
693     }
694
695     /**
696      * @hide
697      */
698     public void setNavigationHandler(XWalkNavigationHandler handler) {
699         if (mContent == null) return;
700         checkThreadSafety();
701         mContent.setNavigationHandler(handler);
702     }
703
704     /**
705      * @hide
706      */
707     public void setNotificationService(XWalkNotificationService service) {
708         if (mContent == null) return;
709         checkThreadSafety();
710         mContent.setNotificationService(service);
711     }
712 }