// Copyright 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.content.browser; import android.content.Context; import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Rect; import android.os.Build; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.widget.FrameLayout; import com.google.common.annotations.VisibleForTesting; import org.chromium.base.TraceEvent; import org.chromium.ui.base.WindowAndroid; /** * The containing view for {@link ContentViewCore} that exists in the Android UI hierarchy and * exposes the various {@link View} functionality to it. * * TODO(joth): Remove any methods overrides from this class that were added for WebView * compatibility. */ public class ContentView extends FrameLayout implements ContentViewCore.InternalAccessDelegate, PageInfo { private final ContentViewCore mContentViewCore; private float mCurrentTouchOffsetX; private float mCurrentTouchOffsetY; private final int[] mLocationInWindow = new int[2]; /** * Creates an instance of a ContentView. * @param context The Context the view is running in, through which it can * access the current theme, resources, etc. * @param nativeWebContents A pointer to the native web contents. * @param windowAndroid An instance of the WindowAndroid. * @return A ContentView instance. */ public static ContentView newInstance(Context context, long nativeWebContents, WindowAndroid windowAndroid) { return newInstance(context, nativeWebContents, windowAndroid, null, android.R.attr.webViewStyle); } /** * Creates an instance of a ContentView. * @param context The Context the view is running in, through which it can * access the current theme, resources, etc. * @param nativeWebContents A pointer to the native web contents. * @param windowAndroid An instance of the WindowAndroid. * @param attrs The attributes of the XML tag that is inflating the view. * @return A ContentView instance. */ public static ContentView newInstance(Context context, long nativeWebContents, WindowAndroid windowAndroid, AttributeSet attrs) { // TODO(klobag): use the WebViewStyle as the default style for now. It enables scrollbar. // When ContentView is moved to framework, we can define its own style in the res. return newInstance(context, nativeWebContents, windowAndroid, attrs, android.R.attr.webViewStyle); } /** * Creates an instance of a ContentView. * @param context The Context the view is running in, through which it can * access the current theme, resources, etc. * @param nativeWebContents A pointer to the native web contents. * @param windowAndroid An instance of the WindowAndroid. * @param attrs The attributes of the XML tag that is inflating the view. * @param defStyle The default style to apply to this view. * @return A ContentView instance. */ public static ContentView newInstance(Context context, long nativeWebContents, WindowAndroid windowAndroid, AttributeSet attrs, int defStyle) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { return new ContentView(context, nativeWebContents, windowAndroid, attrs, defStyle); } else { return new JellyBeanContentView(context, nativeWebContents, windowAndroid, attrs, defStyle); } } protected ContentView(Context context, long nativeWebContents, WindowAndroid windowAndroid, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); if (getScrollBarStyle() == View.SCROLLBARS_INSIDE_OVERLAY) { setHorizontalScrollBarEnabled(false); setVerticalScrollBarEnabled(false); } setFocusable(true); setFocusableInTouchMode(true); mContentViewCore = new ContentViewCore(context); mContentViewCore.initialize(this, this, nativeWebContents, windowAndroid); } /** * @return The URL of the page. */ public String getUrl() { return mContentViewCore.getUrl(); } // PageInfo implementation. @Override public String getTitle() { return mContentViewCore.getTitle(); } @Override public int getBackgroundColor() { return mContentViewCore.getBackgroundColor(); } @Override public View getView() { return this; } /** * @return The core component of the ContentView that handles JNI communication. Should only be * used for passing to native. */ public ContentViewCore getContentViewCore() { return mContentViewCore; } /** * @return The cache of scales and positions used to convert coordinates from/to CSS. */ public RenderCoordinates getRenderCoordinates() { return mContentViewCore.getRenderCoordinates(); } /** * Destroy the internal state of the WebView. This method may only be called * after the WebView has been removed from the view system. No other methods * may be called on this WebView after this method has been called. */ public void destroy() { mContentViewCore.destroy(); } /** * Returns true initially, false after destroy() has been called. * It is illegal to call any other public method after destroy(). */ public boolean isAlive() { return mContentViewCore.isAlive(); } public void setContentViewClient(ContentViewClient client) { mContentViewCore.setContentViewClient(client); } @VisibleForTesting public ContentViewClient getContentViewClient() { return mContentViewCore.getContentViewClient(); } /** * Load url without fixing up the url string. Consumers of ContentView are responsible for * ensuring the URL passed in is properly formatted (i.e. the scheme has been added if left * off during user input). * * @param params Parameters for this load. */ public void loadUrl(LoadUrlParams params) { mContentViewCore.loadUrl(params); } /** * @return Whether the current WebContents has a previous navigation entry. */ public boolean canGoBack() { return mContentViewCore.canGoBack(); } /** * @return Whether the current WebContents has a navigation entry after the current one. */ public boolean canGoForward() { return mContentViewCore.canGoForward(); } /** * Goes to the navigation entry before the current one. */ public void goBack() { mContentViewCore.goBack(); } /** * Goes to the navigation entry following the current one. */ public void goForward() { mContentViewCore.goForward(); } /** * Fling the ContentView from the current position. * @param x Fling touch starting position * @param y Fling touch starting position * @param velocityX Initial velocity of the fling (X) measured in pixels per second. * @param velocityY Initial velocity of the fling (Y) measured in pixels per second. */ @VisibleForTesting public void fling(long timeMs, int x, int y, int velocityX, int velocityY) { mContentViewCore.flingForTest(timeMs, x, y, velocityX, velocityY); } /** * Injects the passed JavaScript code in the current page and evaluates it. * * @throws IllegalStateException If the ContentView has been destroyed. */ public void evaluateJavaScript(String script) throws IllegalStateException { mContentViewCore.evaluateJavaScript(script, null); } /** * To be called when the ContentView is shown. **/ public void onShow() { mContentViewCore.onShow(); } /** * To be called when the ContentView is hidden. **/ public void onHide() { mContentViewCore.onHide(); } /** * Hides the select action bar. */ public void hideSelectActionBar() { mContentViewCore.hideSelectActionBar(); } // FrameLayout overrides. // Needed by ContentViewCore.InternalAccessDelegate @Override public boolean drawChild(Canvas canvas, View child, long drawingTime) { return super.drawChild(canvas, child, drawingTime); } // Needed by ContentViewCore.InternalAccessDelegate @Override public void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); } @Override protected void onSizeChanged(int w, int h, int ow, int oh) { TraceEvent.begin(); super.onSizeChanged(w, h, ow, oh); mContentViewCore.onSizeChanged(w, h, ow, oh); TraceEvent.end(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (changed) { getLocationInWindow(mLocationInWindow); mContentViewCore.onLocationInWindowChanged(mLocationInWindow[0], mLocationInWindow[1]); } } @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { return mContentViewCore.onCreateInputConnection(outAttrs); } @Override public boolean onCheckIsTextEditor() { return mContentViewCore.onCheckIsTextEditor(); } @Override protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { TraceEvent.begin(); super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); mContentViewCore.onFocusChanged(gainFocus); TraceEvent.end(); } @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); mContentViewCore.onWindowFocusChanged(hasWindowFocus); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mContentViewCore.onKeyUp(keyCode, event); } @Override public boolean dispatchKeyEventPreIme(KeyEvent event) { return mContentViewCore.dispatchKeyEventPreIme(event); } @Override public boolean dispatchKeyEvent(KeyEvent event) { if (isFocused()) { return mContentViewCore.dispatchKeyEvent(event); } else { return super.dispatchKeyEvent(event); } } @Override public boolean onTouchEvent(MotionEvent event) { MotionEvent offset = createOffsetMotionEvent(event); boolean consumed = mContentViewCore.onTouchEvent(offset); offset.recycle(); return consumed; } /** * Mouse move events are sent on hover enter, hover move and hover exit. * They are sent on hover exit because sometimes it acts as both a hover * move and hover exit. */ @Override public boolean onHoverEvent(MotionEvent event) { MotionEvent offset = createOffsetMotionEvent(event); boolean consumed = mContentViewCore.onHoverEvent(offset); offset.recycle(); super.onHoverEvent(event); return consumed; } @Override public boolean onGenericMotionEvent(MotionEvent event) { return mContentViewCore.onGenericMotionEvent(event); } @Override public boolean performLongClick() { return false; } /** * Sets the current amount to offset incoming touch events by. This is used to handle content * moving and not lining up properly with the android input system. * @param dx The X offset in pixels to shift touch events. * @param dy The Y offset in pixels to shift touch events. */ public void setCurrentMotionEventOffsets(float dx, float dy) { mCurrentTouchOffsetX = dx; mCurrentTouchOffsetY = dy; } private MotionEvent createOffsetMotionEvent(MotionEvent src) { MotionEvent dst = MotionEvent.obtain(src); dst.offsetLocation(mCurrentTouchOffsetX, mCurrentTouchOffsetY); return dst; } @Override protected void onConfigurationChanged(Configuration newConfig) { mContentViewCore.onConfigurationChanged(newConfig); } /** * Currently the ContentView scrolling happens in the native side. In * the Java view system, it is always pinned at (0, 0). scrollBy() and scrollTo() * are overridden, so that View's mScrollX and mScrollY will be unchanged at * (0, 0). This is critical for drawing ContentView correctly. */ @Override public void scrollBy(int x, int y) { mContentViewCore.scrollBy(x, y); } @Override public void scrollTo(int x, int y) { mContentViewCore.scrollTo(x, y); } @Override protected int computeHorizontalScrollExtent() { // TODO(dtrainor): Need to expose scroll events properly to public. Either make getScroll* // work or expose computeHorizontalScrollOffset()/computeVerticalScrollOffset as public. return mContentViewCore.computeHorizontalScrollExtent(); } @Override protected int computeHorizontalScrollOffset() { return mContentViewCore.computeHorizontalScrollOffset(); } @Override protected int computeHorizontalScrollRange() { return mContentViewCore.computeHorizontalScrollRange(); } @Override protected int computeVerticalScrollExtent() { return mContentViewCore.computeVerticalScrollExtent(); } @Override protected int computeVerticalScrollOffset() { return mContentViewCore.computeVerticalScrollOffset(); } @Override protected int computeVerticalScrollRange() { return mContentViewCore.computeVerticalScrollRange(); } // End FrameLayout overrides. @Override public boolean awakenScrollBars(int startDelay, boolean invalidate) { return mContentViewCore.awakenScrollBars(startDelay, invalidate); } @Override public boolean awakenScrollBars() { return super.awakenScrollBars(); } public int getSingleTapX() { return mContentViewCore.getSingleTapX(); } public int getSingleTapY() { return mContentViewCore.getSingleTapY(); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); mContentViewCore.onInitializeAccessibilityNodeInfo(info); } /** * Fills in scrolling values for AccessibilityEvents. * @param event Event being fired. */ @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); mContentViewCore.onInitializeAccessibilityEvent(event); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mContentViewCore.onAttachedToWindow(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mContentViewCore.onDetachedFromWindow(); } @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); mContentViewCore.onVisibilityChanged(changedView, visibility); } /** * Return the current scale of the WebView * @return The current scale. */ public float getScale() { return mContentViewCore.getScale(); } /** * Enable or disable accessibility features. */ public void setAccessibilityState(boolean state) { mContentViewCore.setAccessibilityState(state); } /** * Inform WebKit that Fullscreen mode has been exited by the user. */ public void exitFullscreen() { mContentViewCore.exitFullscreen(); } /** * Return content scroll y. * * @return The vertical scroll position in pixels. */ public int getContentScrollY() { return mContentViewCore.computeVerticalScrollOffset(); } /** * Return content height. * * @return The height of the content in pixels. */ public int getContentHeight() { return mContentViewCore.computeVerticalScrollRange(); } /////////////////////////////////////////////////////////////////////////////////////////////// // Start Implementation of ContentViewCore.InternalAccessDelegate // /////////////////////////////////////////////////////////////////////////////////////////////// @Override public boolean super_onKeyUp(int keyCode, KeyEvent event) { return super.onKeyUp(keyCode, event); } @Override public boolean super_dispatchKeyEventPreIme(KeyEvent event) { return super.dispatchKeyEventPreIme(event); } @Override public boolean super_dispatchKeyEvent(KeyEvent event) { return super.dispatchKeyEvent(event); } @Override public boolean super_onGenericMotionEvent(MotionEvent event) { return super.onGenericMotionEvent(event); } @Override public void super_onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); } @Override public boolean super_awakenScrollBars(int startDelay, boolean invalidate) { return super.awakenScrollBars(startDelay, invalidate); } /////////////////////////////////////////////////////////////////////////////////////////////// // End Implementation of ContentViewCore.InternalAccessDelegate // /////////////////////////////////////////////////////////////////////////////////////////////// }