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.util.DisplayMetrics;
20import android.view.SurfaceView;
21import android.view.View;
22import android.view.ViewGroup;
23import android.widget.AbsoluteLayout;
24
25import java.util.ArrayList;
26
27class ViewManager {
28    private final WebViewClassic mWebView;
29    private final ArrayList<ChildView> mChildren = new ArrayList<ChildView>();
30    private boolean mHidden;
31    private boolean mReadyToDraw;
32    private boolean mZoomInProgress = false;
33
34    // Threshold at which a surface is prevented from further increasing in size
35    private final int MAX_SURFACE_AREA;
36    // GPU Limit (hard coded for now)
37    private static final int MAX_SURFACE_DIMENSION = 2048;
38
39    class ChildView {
40        int x;
41        int y;
42        int width;
43        int height;
44        View mView; // generic view to show
45
46        ChildView() {
47        }
48
49        void setBounds(int x, int y, int width, int height) {
50            this.x = x;
51            this.y = y;
52            this.width = width;
53            this.height = height;
54        }
55
56        void attachView(int x, int y, int width, int height) {
57            if (mView == null) {
58                return;
59            }
60            setBounds(x, y, width, height);
61
62            mWebView.mPrivateHandler.post(new Runnable() {
63                public void run() {
64                    // This method may be called multiple times. If the view is
65                    // already attached, just set the new LayoutParams,
66                    // otherwise attach the view and add it to the list of
67                    // children.
68                    requestLayout(ChildView.this);
69
70                    if (mView.getParent() == null) {
71                        attachViewOnUIThread();
72                    }
73                }
74            });
75        }
76
77        private void attachViewOnUIThread() {
78            mWebView.getWebView().addView(mView);
79            mChildren.add(this);
80            if (!mReadyToDraw) {
81                mView.setVisibility(View.GONE);
82            }
83        }
84
85        void removeView() {
86            if (mView == null) {
87                return;
88            }
89            mWebView.mPrivateHandler.post(new Runnable() {
90                public void run() {
91                    removeViewOnUIThread();
92                }
93            });
94        }
95
96        private void removeViewOnUIThread() {
97            mWebView.getWebView().removeView(mView);
98            mChildren.remove(this);
99        }
100    }
101
102    ViewManager(WebViewClassic w) {
103        mWebView = w;
104        DisplayMetrics metrics = w.getWebView().getResources().getDisplayMetrics();
105        int pixelArea = metrics.widthPixels * metrics.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                    // setLayoutParams() only requests the layout. If we set it
209                    // to VISIBLE now, it will use the old dimension to set the
210                    // size. Post a message to ensure that it shows the new size.
211                    mWebView.mPrivateHandler.post(new Runnable() {
212                        public void run() {
213                            sView.setVisibility(View.VISIBLE);
214                        }
215                    });
216                } else {
217                    sView.getHolder().setSizeFromLayout();
218                }
219            }
220        }
221    }
222
223    void startZoom() {
224        mZoomInProgress = true;
225        for (ChildView v : mChildren) {
226            requestLayout(v);
227        }
228    }
229
230    void endZoom() {
231        mZoomInProgress = false;
232        for (ChildView v : mChildren) {
233            requestLayout(v);
234        }
235    }
236
237    void scaleAll() {
238        for (ChildView v : mChildren) {
239            requestLayout(v);
240        }
241    }
242
243    void hideAll() {
244        if (mHidden) {
245            return;
246        }
247        for (ChildView v : mChildren) {
248            v.mView.setVisibility(View.GONE);
249        }
250        mHidden = true;
251    }
252
253    void showAll() {
254        if (!mHidden) {
255            return;
256        }
257        for (ChildView v : mChildren) {
258            v.mView.setVisibility(View.VISIBLE);
259        }
260        mHidden = false;
261    }
262
263    void postResetStateAll() {
264        mWebView.mPrivateHandler.post(new Runnable() {
265            public void run() {
266                mReadyToDraw = false;
267            }
268        });
269    }
270
271    void postReadyToDrawAll() {
272        mWebView.mPrivateHandler.post(new Runnable() {
273            public void run() {
274                mReadyToDraw = true;
275                for (ChildView v : mChildren) {
276                    v.mView.setVisibility(View.VISIBLE);
277                }
278            }
279        });
280    }
281
282    ChildView hitTest(int contentX, int contentY) {
283        if (mHidden) {
284            return null;
285        }
286        for (ChildView v : mChildren) {
287            if (v.mView.getVisibility() == View.VISIBLE) {
288                if (contentX >= v.x && contentX < (v.x + v.width)
289                        && contentY >= v.y && contentY < (v.y + v.height)) {
290                    return v;
291                }
292            }
293        }
294        return null;
295    }
296}
297