ViewManager.java revision f066611aab5758b7b2a6b4a525e3f85aa813064a
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.webkit;
18
19import android.view.SurfaceView;
20import android.view.View;
21import android.view.ViewGroup;
22import android.widget.AbsoluteLayout;
23
24import java.util.ArrayList;
25
26class ViewManager {
27    private final WebView mWebView;
28    private final ArrayList<ChildView> mChildren = new ArrayList<ChildView>();
29    private boolean mHidden;
30    private boolean mReadyToDraw;
31    private boolean mZoomInProgress = false;
32
33    // Threshold at which a surface is prevented from further increasing in size
34    private final int MAX_SURFACE_AREA;
35    // GPU Limit (hard coded for now)
36    private static final int MAX_SURFACE_DIMENSION = 2048;
37
38    class ChildView {
39        int x;
40        int y;
41        int width;
42        int height;
43        View mView; // generic view to show
44
45        ChildView() {
46        }
47
48        void setBounds(int x, int y, int width, int height) {
49            this.x = x;
50            this.y = y;
51            this.width = width;
52            this.height = height;
53        }
54
55        void attachView(int x, int y, int width, int height) {
56            if (mView == null) {
57                return;
58            }
59            setBounds(x, y, width, height);
60
61            mWebView.mPrivateHandler.post(new Runnable() {
62                public void run() {
63                    // This method may be called multiple times. If the view is
64                    // already attached, just set the new LayoutParams,
65                    // otherwise attach the view and add it to the list of
66                    // children.
67                    requestLayout(ChildView.this);
68
69                    if (mView.getParent() == null) {
70                        attachViewOnUIThread();
71                    }
72                }
73            });
74        }
75
76        private void attachViewOnUIThread() {
77            mWebView.addView(mView);
78            mChildren.add(this);
79            if (!mReadyToDraw) {
80                mView.setVisibility(View.GONE);
81            }
82        }
83
84        void removeView() {
85            if (mView == null) {
86                return;
87            }
88            mWebView.mPrivateHandler.post(new Runnable() {
89                public void run() {
90                    removeViewOnUIThread();
91                }
92            });
93        }
94
95        private void removeViewOnUIThread() {
96            mWebView.removeView(mView);
97            mChildren.remove(this);
98        }
99    }
100
101    ViewManager(WebView w) {
102        mWebView = w;
103
104        int pixelArea = w.getResources().getDisplayMetrics().widthPixels *
105                        w.getResources().getDisplayMetrics().heightPixels;
106        /* set the threshold to be 275% larger than the screen size. The
107           percentage is simply an estimation and is not based on anything but
108           basic trial-and-error tests run on multiple devices.
109         */
110        MAX_SURFACE_AREA = (int)(pixelArea * 2.75);
111    }
112
113    ChildView createView() {
114        return new ChildView();
115    }
116
117    /**
118     * This should only be called from the UI thread.
119     */
120    private void requestLayout(ChildView v) {
121
122        int width = mWebView.contentToViewDimension(v.width);
123        int height = mWebView.contentToViewDimension(v.height);
124        int x = mWebView.contentToViewX(v.x);
125        int y = mWebView.contentToViewY(v.y);
126
127        AbsoluteLayout.LayoutParams lp;
128        ViewGroup.LayoutParams layoutParams = v.mView.getLayoutParams();
129
130        if (layoutParams instanceof AbsoluteLayout.LayoutParams) {
131            lp = (AbsoluteLayout.LayoutParams) layoutParams;
132            lp.width = width;
133            lp.height = height;
134            lp.x = x;
135            lp.y = y;
136        } else {
137            lp = new AbsoluteLayout.LayoutParams(width, height, x, y);
138        }
139
140        // apply the layout to the view
141        v.mView.setLayoutParams(lp);
142
143        if(v.mView instanceof SurfaceView) {
144
145            final SurfaceView sView = (SurfaceView) v.mView;
146
147            if (sView.isFixedSize() && mZoomInProgress) {
148                /* If we're already fixed, and we're in a zoom, then do nothing
149                   about the size. Just wait until we get called at the end of
150                   the zoom session (with mZoomInProgress false) and we'll
151                   fixup our size then.
152                 */
153                return;
154            }
155
156            /* Compute proportional fixed width/height if necessary.
157             *
158             * NOTE: plugins (e.g. Flash) must not explicitly fix the size of
159             * their surface. The logic below will result in unexpected behavior
160             * for the plugin if they attempt to fix the size of the surface.
161             */
162            int fixedW = width;
163            int fixedH = height;
164            if (fixedW > MAX_SURFACE_DIMENSION || fixedH > MAX_SURFACE_DIMENSION) {
165                if (v.width > v.height) {
166                    fixedW = MAX_SURFACE_DIMENSION;
167                    fixedH = v.height * MAX_SURFACE_DIMENSION / v.width;
168                } else {
169                    fixedH = MAX_SURFACE_DIMENSION;
170                    fixedW = v.width * MAX_SURFACE_DIMENSION / v.height;
171                }
172            }
173            if (fixedW * fixedH > MAX_SURFACE_AREA) {
174                float area = MAX_SURFACE_AREA;
175                if (v.width > v.height) {
176                    fixedW = (int)Math.sqrt(area * v.width / v.height);
177                    fixedH = v.height * fixedW / v.width;
178                } else {
179                    fixedH = (int)Math.sqrt(area * v.height / v.width);
180                    fixedW = v.width * fixedH / v.height;
181                }
182            }
183
184            if (fixedW != width || fixedH != height) {
185                // if we get here, either our dimensions or area (or both)
186                // exeeded our max, so we had to compute fixedW and fixedH
187                sView.getHolder().setFixedSize(fixedW, fixedH);
188            } else if (!sView.isFixedSize() && mZoomInProgress) {
189                // just freeze where we were (view size) until we're done with
190                // the zoom progress
191                sView.getHolder().setFixedSize(sView.getWidth(),
192                                               sView.getHeight());
193            } else if (sView.isFixedSize() && !mZoomInProgress) {
194                /* The changing of visibility is a hack to get around a bug in
195                 * the framework that causes the surface to revert to the size
196                 * it was prior to being fixed before it redraws using the
197                 * values currently in its layout.
198                 *
199                 * The surface is destroyed when it is set to invisible and then
200                 * recreated at the new dimensions when it is made visible. The
201                 * same destroy/create step occurs without the change in
202                 * visibility, but then exhibits the behavior described in the
203                 * previous paragraph.
204                 */
205                if (sView.getVisibility() == View.VISIBLE) {
206                    sView.setVisibility(View.INVISIBLE);
207                    sView.getHolder().setSizeFromLayout();
208                    sView.setVisibility(View.VISIBLE);
209                } else {
210                    sView.getHolder().setSizeFromLayout();
211                }
212            }
213        }
214    }
215
216    void startZoom() {
217        mZoomInProgress = true;
218        for (ChildView v : mChildren) {
219            requestLayout(v);
220        }
221    }
222
223    void endZoom() {
224        mZoomInProgress = false;
225        for (ChildView v : mChildren) {
226            requestLayout(v);
227        }
228    }
229
230    void scaleAll() {
231        for (ChildView v : mChildren) {
232            requestLayout(v);
233        }
234    }
235
236    void hideAll() {
237        if (mHidden) {
238            return;
239        }
240        for (ChildView v : mChildren) {
241            v.mView.setVisibility(View.GONE);
242        }
243        mHidden = true;
244    }
245
246    void showAll() {
247        if (!mHidden) {
248            return;
249        }
250        for (ChildView v : mChildren) {
251            v.mView.setVisibility(View.VISIBLE);
252        }
253        mHidden = false;
254    }
255
256    void postResetStateAll() {
257        mWebView.mPrivateHandler.post(new Runnable() {
258            public void run() {
259                mReadyToDraw = false;
260            }
261        });
262    }
263
264    void postReadyToDrawAll() {
265        mWebView.mPrivateHandler.post(new Runnable() {
266            public void run() {
267                mReadyToDraw = true;
268                for (ChildView v : mChildren) {
269                    v.mView.setVisibility(View.VISIBLE);
270                }
271            }
272        });
273    }
274
275    ChildView hitTest(int contentX, int contentY) {
276        if (mHidden) {
277            return null;
278        }
279        for (ChildView v : mChildren) {
280            if (v.mView.getVisibility() == View.VISIBLE) {
281                if (contentX >= v.x && contentX < (v.x + v.width)
282                        && contentY >= v.y && contentY < (v.y + v.height)) {
283                    return v;
284                }
285            }
286        }
287        return null;
288    }
289}
290