Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / xwalk / runtime / android / core / src / org / xwalk / core / XWalkContent.java
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Copyright (c) 2013-2014 Intel Corporation. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5
6 package org.xwalk.core;
7
8 import android.app.Activity;
9 import android.content.Context;
10 import android.content.Intent;
11 import android.content.SharedPreferences;
12 import android.graphics.Rect;
13 import android.os.Bundle;
14 import android.text.TextUtils;
15 import android.util.AttributeSet;
16 import android.util.Log;
17 import android.view.ViewGroup;
18 import android.webkit.ValueCallback;
19 import android.webkit.WebResourceResponse;
20 import android.widget.FrameLayout;
21
22 import java.io.IOException;
23 import java.io.InputStream;
24
25 import org.chromium.base.CalledByNative;
26 import org.chromium.base.JNINamespace;
27 import org.chromium.base.ThreadUtils;
28 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
29 import org.chromium.content.browser.ContentView;
30 import org.chromium.content.browser.ContentViewCore;
31 import org.chromium.content.browser.ContentViewRenderView;
32 import org.chromium.content.browser.ContentViewRenderView.CompositingSurfaceType;
33 import org.chromium.content.browser.ContentViewStatics;
34 import org.chromium.content.browser.LoadUrlParams;
35 import org.chromium.content.browser.NavigationHistory;
36 import org.chromium.media.MediaPlayerBridge;
37 import org.chromium.ui.base.ActivityWindowAndroid;
38
39 @JNINamespace("xwalk")
40 /**
41  * This class is the implementation class for XWalkView by calling internal
42  * various classes.
43  */
44 class XWalkContent extends FrameLayout implements XWalkPreferences.KeyValueChangeListener {
45     private static String TAG = "XWalkContent";
46     private ContentViewCore mContentViewCore;
47     private ContentView mContentView;
48     private ContentViewRenderView mContentViewRenderView;
49     private ActivityWindowAndroid mWindow;
50     private XWalkDevToolsServer mDevToolsServer;
51     private XWalkView mXWalkView;
52     private XWalkContentsClientBridge mContentsClientBridge;
53     private XWalkContentsIoThreadClient mIoThreadClient;
54     private XWalkWebContentsDelegateAdapter mXWalkContentsDelegateAdapter;
55     private XWalkSettings mSettings;
56     private XWalkGeolocationPermissions mGeolocationPermissions;
57     private XWalkLaunchScreenManager mLaunchScreenManager;
58
59     long mXWalkContent;
60     long mWebContents;
61     boolean mReadyToLoad = false;
62     String mPendingUrl = null;
63     String mPendingData = null;
64
65     public XWalkContent(Context context, AttributeSet attrs, XWalkView xwView) {
66         super(context, attrs);
67
68         // Initialize the WebContensDelegate.
69         mXWalkView = xwView;
70         mContentsClientBridge = new XWalkContentsClientBridge(mXWalkView);
71         mXWalkContentsDelegateAdapter = new XWalkWebContentsDelegateAdapter(
72             mContentsClientBridge);
73         mIoThreadClient = new XWalkIoThreadClientImpl();
74
75         // Initialize mWindow which is needed by content
76         mWindow = new ActivityWindowAndroid(xwView.getActivity());
77
78         // Initialize ContentViewRenderView
79         boolean animated = XWalkPreferences.getValue(XWalkPreferences.ANIMATABLE_XWALK_VIEW);
80         CompositingSurfaceType surfaceType =
81                 animated ? CompositingSurfaceType.TEXTURE_VIEW : CompositingSurfaceType.SURFACE_VIEW;
82         mContentViewRenderView = new ContentViewRenderView(context, mWindow, surfaceType) {
83             protected void onReadyToRender() {
84                 if (mPendingUrl != null) {
85                     doLoadUrl(mPendingUrl, mPendingData);
86                     mPendingUrl = null;
87                     mPendingData = null;
88                 }
89
90                 mReadyToLoad = true;
91             }
92         };
93         mLaunchScreenManager = new XWalkLaunchScreenManager(context, mXWalkView);
94         mContentViewRenderView.registerFirstRenderedFrameListener(mLaunchScreenManager);
95         addView(mContentViewRenderView,
96                 new FrameLayout.LayoutParams(
97                         FrameLayout.LayoutParams.MATCH_PARENT,
98                         FrameLayout.LayoutParams.MATCH_PARENT));
99
100         mXWalkContent = nativeInit(mXWalkContentsDelegateAdapter, mContentsClientBridge);
101         mWebContents = nativeGetWebContents(mXWalkContent, mIoThreadClient,
102                 mContentsClientBridge.getInterceptNavigationDelegate());
103
104         // Initialize ContentView.
105         mContentView = ContentView.newInstance(getContext(), mWebContents, mWindow);
106         addView(mContentView,
107                 new FrameLayout.LayoutParams(
108                         FrameLayout.LayoutParams.MATCH_PARENT,
109                         FrameLayout.LayoutParams.MATCH_PARENT));
110         mContentView.getContentViewCore().setContentViewClient(mContentsClientBridge);
111
112         mContentViewCore = mContentView.getContentViewCore();
113         mContentViewRenderView.setCurrentContentViewCore(mContentViewCore);
114         // For addJavascriptInterface
115         mContentsClientBridge.installWebContentsObserver(mContentViewCore);
116
117         mContentViewCore.setDownloadDelegate(mContentsClientBridge);
118
119         // Set the third argument isAccessFromFileURLsGrantedByDefault to false, so that
120         // the members mAllowUniversalAccessFromFileURLs and mAllowFileAccessFromFileURLs
121         // won't be changed from false to true at the same time in the constructor of
122         // XWalkSettings class.
123         mSettings = new XWalkSettings(getContext(), mWebContents, false);
124         // Enable AllowFileAccessFromFileURLs, so that files under file:// path could be
125         // loaded by XMLHttpRequest.
126         mSettings.setAllowFileAccessFromFileURLs(true);
127
128         SharedPreferences sharedPreferences = new InMemorySharedPreferences();
129         mGeolocationPermissions = new XWalkGeolocationPermissions(sharedPreferences);
130
131         MediaPlayerBridge.setResourceLoadingFilter(
132                 new XWalkMediaPlayerResourceLoadingFilter());
133
134         XWalkPreferences.load(this);
135     }
136
137     void doLoadUrl(String url, String content) {
138         // Handle the same url loading by parameters.
139         if (url != null && !url.isEmpty() &&
140                 TextUtils.equals(url, mContentViewCore.getUrl())) {
141             mContentViewCore.reload(true);
142         } else {
143             LoadUrlParams params = null;
144             if (content == null || content.isEmpty()) {
145                 params = new LoadUrlParams(url);
146             } else {
147                 params = LoadUrlParams.createLoadDataParamsWithBaseUrl(
148                         content, "text/html", false, url, null);
149             }
150             params.setOverrideUserAgent(LoadUrlParams.UA_OVERRIDE_TRUE);
151             mContentViewCore.loadUrl(params);
152         }
153
154         mContentView.requestFocus();
155     }
156
157     public void loadUrl(String url, String data) {
158         if ((url == null || url.isEmpty()) &&
159                 (data == null || data.isEmpty())) {
160             return;
161         }
162
163         if (mReadyToLoad) {
164             doLoadUrl(url, data);
165         } else {
166             mPendingUrl = url;
167             mPendingData = data;
168         }
169     }
170
171     public void reload(int mode) {
172         if (mReadyToLoad) {
173             switch (mode) {
174                 case XWalkView.RELOAD_IGNORE_CACHE:
175                     mContentViewCore.reloadIgnoringCache(true);
176                     break;
177                 case XWalkView.RELOAD_NORMAL:
178                 default:
179                     mContentViewCore.reload(true);
180
181             }
182         }
183     }
184
185     public String getUrl() {
186         String url = mContentViewCore.getUrl();
187         if (url == null || url.trim().isEmpty()) return null;
188         return url;
189     }
190
191     public String getTitle() {
192         String title = mContentViewCore.getTitle().trim();
193         if (title == null) title = "";
194         return title;
195     }
196
197     public void addJavascriptInterface(Object object, String name) {
198         mContentViewCore.addPossiblyUnsafeJavascriptInterface(object, name,
199                 JavascriptInterface.class);
200     }
201
202     public void evaluateJavascript(String script, ValueCallback<String> callback) {
203         final ValueCallback<String>  fCallback = callback;
204         ContentViewCore.JavaScriptCallback coreCallback = null;
205         if (fCallback != null) {
206             coreCallback = new ContentViewCore.JavaScriptCallback() {
207                 @Override
208                 public void handleJavaScriptResult(String jsonResult) {
209                     fCallback.onReceiveValue(jsonResult);
210                 }
211             };
212         }
213         mContentViewCore.evaluateJavaScript(script, coreCallback);
214     }
215
216     public void setUIClient(XWalkUIClient client) {
217         mContentsClientBridge.setUIClient(client);
218     }
219
220     public void setResourceClient(XWalkResourceClient client) {
221         mContentsClientBridge.setResourceClient(client);
222     }
223
224     public void setXWalkWebChromeClient(XWalkWebChromeClient client) {
225         mContentsClientBridge.setXWalkWebChromeClient(client);
226     }
227
228     public XWalkWebChromeClient getXWalkWebChromeClient() {
229         return mContentsClientBridge.getXWalkWebChromeClient();
230     }
231
232     public void setXWalkClient(XWalkClient client) {
233         mContentsClientBridge.setXWalkClient(client);
234     }
235
236     public void setDownloadListener(DownloadListener listener) {
237         mContentsClientBridge.setDownloadListener(listener);
238     }
239
240     public void setNavigationHandler(XWalkNavigationHandler handler) {
241         mContentsClientBridge.setNavigationHandler(handler);
242     }
243
244     public void setNotificationService(XWalkNotificationService service) {
245         mContentsClientBridge.setNotificationService(service);
246     }
247
248     public void onPause() {
249         mContentViewCore.onHide();
250     }
251
252     public void onResume() {
253         mContentViewCore.onShow();
254     }
255
256     public void onActivityResult(int requestCode, int resultCode, Intent data) {
257         mWindow.onActivityResult(requestCode, resultCode, data);
258     }
259
260     public boolean onNewIntent(Intent intent) {
261         return mContentsClientBridge.onNewIntent(intent);
262     }
263
264     public void clearCache(boolean includeDiskFiles) {
265         if (mXWalkContent == 0) return;
266         nativeClearCache(mXWalkContent, includeDiskFiles);
267     }
268
269     public void clearHistory() {
270         mContentViewCore.clearHistory();
271     }
272
273     public boolean canGoBack() {
274         return mContentViewCore.canGoBack();
275     }
276
277     public void goBack() {
278         mContentViewCore.goBack();
279     }
280
281     public boolean canGoForward() {
282         return mContentViewCore.canGoForward();
283     }
284
285     public void goForward() {
286         mContentViewCore.goForward();
287     }
288
289     void navigateTo(int offset)  {
290         mContentViewCore.goToOffset(offset);
291     }
292
293     public void stopLoading() {
294         mContentViewCore.stopLoading();
295     }
296
297     // TODO(Guangzhen): ContentViewStatics will be removed in upstream,
298     // details in content_view_statics.cc.
299     // We need follow up after upstream updates that.
300     public void pauseTimers() {
301         ContentViewStatics.setWebKitSharedTimersSuspended(true);
302     }
303
304     public void resumeTimers() {
305         ContentViewStatics.setWebKitSharedTimersSuspended(false);
306     }
307
308     public String getOriginalUrl() {
309         NavigationHistory history = mContentViewCore.getNavigationHistory();
310         int currentIndex = history.getCurrentEntryIndex();
311         if (currentIndex >= 0 && currentIndex < history.getEntryCount()) {
312             return history.getEntryAtIndex(currentIndex).getOriginalUrl();
313         }
314         return null;
315     }
316
317     public String getXWalkVersion() {
318         if (mXWalkContent == 0) return "";
319         return nativeGetVersion(mXWalkContent);
320     }
321
322     public void setNetworkAvailable(boolean networkUp) {
323         if (mXWalkContent == 0) return;
324         nativeSetJsOnlineProperty(mXWalkContent, networkUp);
325     }
326
327     // For instrumentation test.
328     public ContentViewCore getContentViewCoreForTest() {
329         return mContentViewCore;
330     }
331
332     // For instrumentation test.
333     public void installWebContentsObserverForTest(XWalkContentsClient contentClient) {
334         contentClient.installWebContentsObserver(mContentViewCore);
335     }
336
337     public String devToolsAgentId() {
338         if (mXWalkContent == 0) return "";
339         return nativeDevToolsAgentId(mXWalkContent);
340     }
341
342     public XWalkSettings getSettings() {
343         return mSettings;
344     }
345
346     public void loadAppFromManifest(String url, String data) {
347         if (mXWalkContent == 0 ||
348                 ((url == null || url.isEmpty()) &&
349                         (data == null || data.isEmpty()))) {
350             return;
351         }
352
353         String content = data;
354         // If the data of manifest.json is not set, try to load it.
355         if (data == null || data.isEmpty()) {
356             try {
357                 content = AndroidProtocolHandler.getUrlContent(mXWalkView.getActivity(), url);
358             } catch (IOException e) {
359                 throw new RuntimeException("Failed to read the manifest: " + url);
360             }
361         }
362
363         // Calculate the base url of manifestUrl. Used by native side.
364         // TODO(yongsheng): It's from runtime side. Need to find a better way
365         // to get base url.
366         String baseUrl = url;
367         int position = url.lastIndexOf("/");
368         if (position != -1) {
369             baseUrl = url.substring(0, position + 1);
370         } else {
371             Log.w(TAG, "The url of manifest.json is probably not set correctly.");
372         }
373
374         if (!nativeSetManifest(mXWalkContent, baseUrl, content)) {
375             throw new RuntimeException("Failed to parse the manifest file: " + url);
376         }
377     }
378
379     public XWalkNavigationHistory getNavigationHistory() {
380         return new XWalkNavigationHistory(mXWalkView, mContentViewCore.getNavigationHistory());
381     }
382
383     public static final String SAVE_RESTORE_STATE_KEY = "XWALKVIEW_STATE";
384
385     public XWalkNavigationHistory saveState(Bundle outState) {
386         if (outState == null) return null;
387
388         byte[] state = nativeGetState(mXWalkContent);
389         if (state == null) return null;
390
391         outState.putByteArray(SAVE_RESTORE_STATE_KEY, state);
392         return getNavigationHistory();
393     }
394
395     public XWalkNavigationHistory restoreState(Bundle inState) {
396         if (inState == null) return null;
397
398         byte[] state = inState.getByteArray(SAVE_RESTORE_STATE_KEY);
399         if (state == null) return null;
400
401         boolean result = nativeSetState(mXWalkContent, state);
402
403         // The onUpdateTitle callback normally happens when a page is loaded,
404         // but is optimized out in the restoreState case because the title is
405         // already restored. See WebContentsImpl::UpdateTitleForEntry. So we
406         // call the callback explicitly here.
407         if (result) {
408             mContentsClientBridge.onUpdateTitle(mContentViewCore.getTitle());
409         }
410
411         return result ? getNavigationHistory() : null;
412     }
413
414     boolean hasEnteredFullscreen() {
415         return mContentsClientBridge.hasEnteredFullscreen();
416     }
417
418     void exitFullscreen() {
419         if (hasEnteredFullscreen()) {
420             mContentsClientBridge.exitFullscreen(mWebContents);
421         }
422     }
423
424     @CalledByNative
425     public void onGetUrlFromManifest(String url) {
426         if (url != null && !url.isEmpty()) {
427             loadUrl(url, null);
428         }
429     }
430
431     @CalledByNative
432     public void onGetUrlAndLaunchScreenFromManifest(String url, String readyWhen, String imageBorder) {
433         if (url == null || url.isEmpty()) return;
434         mLaunchScreenManager.displayLaunchScreen(readyWhen, imageBorder);
435         mContentsClientBridge.registerPageLoadListener(mLaunchScreenManager);
436         loadUrl(url, null);
437     }
438
439     @CalledByNative
440     public void onGetFullscreenFlagFromManifest(boolean enterFullscreen) {
441         if (enterFullscreen) mContentsClientBridge.onToggleFullscreen(true);
442     }
443
444     public void destroy() {
445         if (mXWalkContent == 0) return;
446
447         XWalkPreferences.unload(this);
448         // Reset existing notification service in order to destruct it.
449         setNotificationService(null);
450         // Remove its children used for page rendering from view hierarchy.
451         removeView(mContentView);
452         removeView(mContentViewRenderView);
453         mContentViewRenderView.setCurrentContentViewCore(null);
454
455         // Destroy the native resources.
456         mContentViewRenderView.destroy();
457         mContentViewCore.destroy();
458
459         nativeDestroy(mXWalkContent);
460         mXWalkContent = 0;
461     }
462
463     public int getRoutingID() {
464         return nativeGetRoutingID(mXWalkContent);
465     }
466
467     //--------------------------------------------------------------------------------------------
468     private class XWalkIoThreadClientImpl implements XWalkContentsIoThreadClient {
469         // All methods are called on the IO thread.
470
471         @Override
472         public int getCacheMode() {
473             return mSettings.getCacheMode();
474         }
475
476         @Override
477         public InterceptedRequestData shouldInterceptRequest(final String url,
478                 boolean isMainFrame) {
479
480             // Notify a resource load is started. This is not the best place to start the callback
481             // but it's a workable way.
482             mContentsClientBridge.getCallbackHelper().postOnResourceLoadStarted(url);
483
484             WebResourceResponse webResourceResponse = mContentsClientBridge.shouldInterceptRequest(url);
485             InterceptedRequestData interceptedRequestData = null;
486
487             if (webResourceResponse == null) {
488                 mContentsClientBridge.getCallbackHelper().postOnLoadResource(url);
489             } else {
490                 if (isMainFrame && webResourceResponse.getData() == null) {
491                     mContentsClientBridge.getCallbackHelper().postOnReceivedError(
492                             XWalkResourceClient.ERROR_UNKNOWN, null, url);
493                 }
494                 interceptedRequestData = new InterceptedRequestData(webResourceResponse.getMimeType(),
495                                                                     webResourceResponse.getEncoding(),
496                                                                     webResourceResponse.getData());
497             }
498             return interceptedRequestData;
499         }
500
501         @Override
502         public boolean shouldBlockContentUrls() {
503             return !mSettings.getAllowContentAccess();
504         }
505
506         @Override
507         public boolean shouldBlockFileUrls() {
508             return !mSettings.getAllowFileAccess();
509         }
510
511         @Override
512         public boolean shouldBlockNetworkLoads() {
513             return mSettings.getBlockNetworkLoads();
514         }
515
516         @Override
517         public void onDownloadStart(String url,
518                                     String userAgent,
519                                     String contentDisposition,
520                                     String mimeType,
521                                     long contentLength) {
522             mContentsClientBridge.getCallbackHelper().postOnDownloadStart(url, userAgent,
523                     contentDisposition, mimeType, contentLength);
524         }
525
526         @Override
527         public void newLoginRequest(String realm, String account, String args) {
528             mContentsClientBridge.getCallbackHelper().postOnReceivedLoginRequest(realm, account, args);
529         }
530     }
531
532     private class XWalkGeolocationCallback implements XWalkGeolocationPermissions.Callback {
533         @Override
534         public void invoke(final String origin, final boolean allow, final boolean retain) {
535             ThreadUtils.runOnUiThread(new Runnable() {
536                 @Override
537                 public void run() {
538                     if (retain) {
539                         if (allow) {
540                             mGeolocationPermissions.allow(origin);
541                         } else {
542                             mGeolocationPermissions.deny(origin);
543                         }
544                     }
545                     nativeInvokeGeolocationCallback(mXWalkContent, allow, origin);
546                 }
547             });
548         }
549     }
550
551     @CalledByNative
552     private void onGeolocationPermissionsShowPrompt(String origin) {
553         // Reject if geolocation is disabled, or the origin has a retained deny.
554         if (!mSettings.getGeolocationEnabled()) {
555             nativeInvokeGeolocationCallback(mXWalkContent, false, origin);
556             return;
557         }
558         // Allow if the origin has a retained allow.
559         if (mGeolocationPermissions.hasOrigin(origin)) {
560             nativeInvokeGeolocationCallback(mXWalkContent,
561                     mGeolocationPermissions.isOriginAllowed(origin),
562                     origin);
563             return;
564         }
565         mContentsClientBridge.onGeolocationPermissionsShowPrompt(
566                 origin, new XWalkGeolocationCallback());
567     }
568
569     @CalledByNative
570     public void onGeolocationPermissionsHidePrompt() {
571         mContentsClientBridge.onGeolocationPermissionsHidePrompt();
572     }
573
574     public String enableRemoteDebugging(int allowedUid) {
575         // Chrome looks for "devtools_remote" pattern in the name of a unix domain socket
576         // to identify a debugging page
577         final String socketName = getContext().getApplicationContext().getPackageName() + "_devtools_remote";
578         if (mDevToolsServer == null) {
579             mDevToolsServer = new XWalkDevToolsServer(socketName);
580             mDevToolsServer.allowConnectionFromUid(allowedUid);
581             mDevToolsServer.setRemoteDebuggingEnabled(true);
582         }
583         // devtools/page is hardcoded in devtools_http_handler_impl.cc (kPageUrlPrefix)
584         return "ws://" + socketName + "/devtools/page/" + devToolsAgentId();
585     }
586
587     // Enables remote debugging and returns the URL at which the dev tools server is listening
588     // for commands. Only the current process is allowed to connect to the server.
589     String enableRemoteDebugging() {
590         return enableRemoteDebugging(getContext().getApplicationInfo().uid);
591     }
592
593     void disableRemoteDebugging() {
594         if (mDevToolsServer ==  null) return;
595
596         if (mDevToolsServer.isRemoteDebuggingEnabled()) {
597             mDevToolsServer.setRemoteDebuggingEnabled(false);
598         }
599         mDevToolsServer.destroy();
600         mDevToolsServer = null;
601     }
602
603     @Override
604     public void onKeyValueChanged(String key, boolean value) {
605         if (key == XWalkPreferences.REMOTE_DEBUGGING) {
606             if (value) enableRemoteDebugging();
607             else disableRemoteDebugging();
608         }
609     }
610
611     public void setOverlayVideoMode(boolean enabled) {
612         if (mContentViewRenderView != null) {
613             mContentViewRenderView.setOverlayVideoMode(enabled);
614         }
615     }
616
617     private native long nativeInit(XWalkWebContentsDelegate webViewContentsDelegate,
618             XWalkContentsClientBridge bridge);
619     private static native void nativeDestroy(long nativeXWalkContent);
620     private native long nativeGetWebContents(long nativeXWalkContent,
621             XWalkContentsIoThreadClient ioThreadClient,
622             InterceptNavigationDelegate delegate);
623     private native void nativeClearCache(long nativeXWalkContent, boolean includeDiskFiles);
624     private native String nativeDevToolsAgentId(long nativeXWalkContent);
625     private native String nativeGetVersion(long nativeXWalkContent);
626     private native void nativeSetJsOnlineProperty(long nativeXWalkContent, boolean networkUp);
627     private native boolean nativeSetManifest(long nativeXWalkContent, String path, String manifest);
628     private native int nativeGetRoutingID(long nativeXWalkContent);
629     private native void nativeInvokeGeolocationCallback(
630             long nativeXWalkContent, boolean value, String requestingFrame);
631     private native byte[] nativeGetState(long nativeXWalkContent);
632     private native boolean nativeSetState(long nativeXWalkContent, byte[] state);
633 }