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