1// Copyright 2012 The Chromium Authors. 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
5package org.chromium.android_webview;
6
7import android.view.View;
8import android.view.View.MeasureSpec;
9
10/**
11 * Helper methods used to manage the layout of the View that contains AwContents.
12 */
13public class AwLayoutSizer {
14    // These are used to prevent a re-layout if the content size changes within a dimension that is
15    // fixed by the view system.
16    private boolean mWidthMeasurementIsFixed;
17    private boolean mHeightMeasurementIsFixed;
18
19    // Size of the rendered content, as reported by native.
20    private int mContentHeightCss;
21    private int mContentWidthCss;
22
23    // Page scale factor. This is set to zero initially so that we don't attempt to do a layout if
24    // we get the content size change notification first and a page scale change second.
25    private float mPageScaleFactor = 0.0f;
26
27    // Whether to postpone layout requests.
28    private boolean mFreezeLayoutRequests;
29    // Did we try to request a layout since the last time mPostponeLayoutRequests was set to true.
30    private boolean mFrozenLayoutRequestPending;
31
32    private double mDIPScale;
33
34    // Was our height larger than the AT_MOST constraint the last time onMeasure was called?
35    private boolean mHeightMeasurementLimited;
36    // If mHeightMeasurementLimited is true then this contains the height limit.
37    private int mHeightMeasurementLimit;
38
39    // Callback object for interacting with the View.
40    private Delegate mDelegate;
41
42    /**
43     * Delegate interface through which the AwLayoutSizer communicates with the view it's sizing.
44     */
45    public interface Delegate {
46        void requestLayout();
47        void setMeasuredDimension(int measuredWidth, int measuredHeight);
48        boolean isLayoutParamsHeightWrapContent();
49        void setForceZeroLayoutHeight(boolean forceZeroHeight);
50    }
51
52    /**
53     * Default constructor. Note: both setDelegate and setDIPScale must be called before the class
54     * is ready for use.
55     */
56    public AwLayoutSizer() {
57    }
58
59    public void setDelegate(Delegate delegate) {
60        mDelegate = delegate;
61    }
62
63    public void setDIPScale(double dipScale) {
64        mDIPScale = dipScale;
65    }
66
67    /**
68     * Postpone requesting layouts till unfreezeLayoutRequests is called.
69     */
70    public void freezeLayoutRequests() {
71        mFreezeLayoutRequests = true;
72        mFrozenLayoutRequestPending = false;
73    }
74
75    /**
76     * Stop postponing layout requests and request layout if such a request would have been made
77     * had the freezeLayoutRequests method not been called before.
78     */
79    public void unfreezeLayoutRequests() {
80        mFreezeLayoutRequests = false;
81        if (mFrozenLayoutRequestPending) {
82            mFrozenLayoutRequestPending = false;
83            mDelegate.requestLayout();
84        }
85    }
86
87    /**
88     * Update the contents size.
89     * This should be called whenever the content size changes (due to DOM manipulation or page
90     * load, for example).
91     * The width and height should be in CSS pixels.
92     */
93    public void onContentSizeChanged(int widthCss, int heightCss) {
94        doUpdate(widthCss, heightCss, mPageScaleFactor);
95    }
96
97    /**
98     * Update the contents page scale.
99     * This should be called whenever the content page scale factor changes (due to pinch zoom, for
100     * example).
101     */
102    public void onPageScaleChanged(float pageScaleFactor) {
103        doUpdate(mContentWidthCss, mContentHeightCss, pageScaleFactor);
104    }
105
106    private void doUpdate(int widthCss, int heightCss, float pageScaleFactor) {
107        // We want to request layout only if the size or scale change, however if any of the
108        // measurements are 'fixed', then changing the underlying size won't have any effect, so we
109        // ignore changes to dimensions that are 'fixed'.
110        final int heightPix = (int) (heightCss * mPageScaleFactor * mDIPScale);
111        boolean pageScaleChanged = mPageScaleFactor != pageScaleFactor;
112        boolean contentHeightChangeMeaningful = !mHeightMeasurementIsFixed &&
113            (!mHeightMeasurementLimited || heightPix < mHeightMeasurementLimit);
114        boolean pageScaleChangeMeaningful =
115            !mWidthMeasurementIsFixed || contentHeightChangeMeaningful;
116        boolean layoutNeeded = (mContentWidthCss != widthCss && !mWidthMeasurementIsFixed) ||
117            (mContentHeightCss != heightCss && contentHeightChangeMeaningful) ||
118            (pageScaleChanged && pageScaleChangeMeaningful);
119
120        mContentWidthCss = widthCss;
121        mContentHeightCss = heightCss;
122        mPageScaleFactor = pageScaleFactor;
123
124        if (layoutNeeded) {
125            if (mFreezeLayoutRequests) {
126                mFrozenLayoutRequestPending = true;
127            } else {
128                mDelegate.requestLayout();
129            }
130        }
131    }
132
133    /**
134     * Calculate the size of the view.
135     * This is designed to be used to implement the android.view.View#onMeasure() method.
136     */
137    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
138        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
139        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
140        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
141        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
142
143        int contentHeightPix = (int) (mContentHeightCss * mPageScaleFactor * mDIPScale);
144        int contentWidthPix = (int) (mContentWidthCss * mPageScaleFactor * mDIPScale);
145
146        int measuredHeight = contentHeightPix;
147        int measuredWidth = contentWidthPix;
148
149        // Always use the given size unless unspecified. This matches WebViewClassic behavior.
150        mWidthMeasurementIsFixed = (widthMode != MeasureSpec.UNSPECIFIED);
151        mHeightMeasurementIsFixed = (heightMode == MeasureSpec.EXACTLY);
152        mHeightMeasurementLimited =
153            (heightMode == MeasureSpec.AT_MOST) && (contentHeightPix > heightSize);
154        mHeightMeasurementLimit = heightSize;
155
156        if (mHeightMeasurementIsFixed || mHeightMeasurementLimited) {
157            measuredHeight = heightSize;
158        }
159
160        if (mWidthMeasurementIsFixed) {
161            measuredWidth = widthSize;
162        }
163
164        if (measuredHeight < contentHeightPix) {
165            measuredHeight |= View.MEASURED_STATE_TOO_SMALL;
166        }
167
168        if (measuredWidth < contentWidthPix) {
169            measuredWidth |= View.MEASURED_STATE_TOO_SMALL;
170        }
171
172        mDelegate.setMeasuredDimension(measuredWidth, measuredHeight);
173    }
174
175    /**
176     * Notify the AwLayoutSizer that the size of the view has changed.
177     * This should be called by the Android view system after onMeasure if the view's size has
178     * changed.
179     */
180    public void onSizeChanged(int w, int h, int ow, int oh) {
181        updateLayoutSettings();
182    }
183
184    /**
185     * Notify the AwLayoutSizer that the layout pass requested via Delegate.requestLayout has
186     * completed.
187     * This should be called after onSizeChanged regardless of whether the size has changed or not.
188     */
189    public void onLayoutChange() {
190        updateLayoutSettings();
191    }
192
193    // This needs to be called every time either the physical size of the view is changed or layout
194    // params are updated.
195    private void updateLayoutSettings() {
196        mDelegate.setForceZeroLayoutHeight(mDelegate.isLayoutParamsHeightWrapContent());
197    }
198}
199