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