| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.webkit; |
| |
| import android.view.SurfaceView; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.AbsoluteLayout; |
| |
| import java.util.ArrayList; |
| |
| class ViewManager { |
| private final WebView mWebView; |
| private final ArrayList<ChildView> mChildren = new ArrayList<ChildView>(); |
| private boolean mHidden; |
| private boolean mReadyToDraw; |
| private boolean mZoomInProgress = false; |
| |
| // Threshold at which a surface is prevented from further increasing in size |
| private final int MAX_SURFACE_AREA; |
| // GPU Limit (hard coded for now) |
| private static final int MAX_SURFACE_DIMENSION = 2048; |
| |
| class ChildView { |
| int x; |
| int y; |
| int width; |
| int height; |
| View mView; // generic view to show |
| |
| ChildView() { |
| } |
| |
| void setBounds(int x, int y, int width, int height) { |
| this.x = x; |
| this.y = y; |
| this.width = width; |
| this.height = height; |
| } |
| |
| void attachView(int x, int y, int width, int height) { |
| if (mView == null) { |
| return; |
| } |
| setBounds(x, y, width, height); |
| |
| mWebView.mPrivateHandler.post(new Runnable() { |
| public void run() { |
| // This method may be called multiple times. If the view is |
| // already attached, just set the new LayoutParams, |
| // otherwise attach the view and add it to the list of |
| // children. |
| requestLayout(ChildView.this); |
| |
| if (mView.getParent() == null) { |
| attachViewOnUIThread(); |
| } |
| } |
| }); |
| } |
| |
| private void attachViewOnUIThread() { |
| mWebView.addView(mView); |
| mChildren.add(this); |
| if (!mReadyToDraw) { |
| mView.setVisibility(View.GONE); |
| } |
| } |
| |
| void removeView() { |
| if (mView == null) { |
| return; |
| } |
| mWebView.mPrivateHandler.post(new Runnable() { |
| public void run() { |
| removeViewOnUIThread(); |
| } |
| }); |
| } |
| |
| private void removeViewOnUIThread() { |
| mWebView.removeView(mView); |
| mChildren.remove(this); |
| } |
| } |
| |
| ViewManager(WebView w) { |
| mWebView = w; |
| |
| int pixelArea = w.getResources().getDisplayMetrics().widthPixels * |
| w.getResources().getDisplayMetrics().heightPixels; |
| /* set the threshold to be 275% larger than the screen size. The |
| percentage is simply an estimation and is not based on anything but |
| basic trial-and-error tests run on multiple devices. |
| */ |
| MAX_SURFACE_AREA = (int)(pixelArea * 2.75); |
| } |
| |
| ChildView createView() { |
| return new ChildView(); |
| } |
| |
| /** |
| * This should only be called from the UI thread. |
| */ |
| private void requestLayout(ChildView v) { |
| |
| int width = mWebView.contentToViewDimension(v.width); |
| int height = mWebView.contentToViewDimension(v.height); |
| int x = mWebView.contentToViewX(v.x); |
| int y = mWebView.contentToViewY(v.y); |
| |
| AbsoluteLayout.LayoutParams lp; |
| ViewGroup.LayoutParams layoutParams = v.mView.getLayoutParams(); |
| |
| if (layoutParams instanceof AbsoluteLayout.LayoutParams) { |
| lp = (AbsoluteLayout.LayoutParams) layoutParams; |
| lp.width = width; |
| lp.height = height; |
| lp.x = x; |
| lp.y = y; |
| } else { |
| lp = new AbsoluteLayout.LayoutParams(width, height, x, y); |
| } |
| |
| // apply the layout to the view |
| v.mView.setLayoutParams(lp); |
| |
| if(v.mView instanceof SurfaceView) { |
| |
| final SurfaceView sView = (SurfaceView) v.mView; |
| |
| if (sView.isFixedSize() && mZoomInProgress) { |
| /* If we're already fixed, and we're in a zoom, then do nothing |
| about the size. Just wait until we get called at the end of |
| the zoom session (with mZoomInProgress false) and we'll |
| fixup our size then. |
| */ |
| return; |
| } |
| |
| /* Compute proportional fixed width/height if necessary. |
| * |
| * NOTE: plugins (e.g. Flash) must not explicitly fix the size of |
| * their surface. The logic below will result in unexpected behavior |
| * for the plugin if they attempt to fix the size of the surface. |
| */ |
| int fixedW = width; |
| int fixedH = height; |
| if (fixedW > MAX_SURFACE_DIMENSION || fixedH > MAX_SURFACE_DIMENSION) { |
| if (v.width > v.height) { |
| fixedW = MAX_SURFACE_DIMENSION; |
| fixedH = v.height * MAX_SURFACE_DIMENSION / v.width; |
| } else { |
| fixedH = MAX_SURFACE_DIMENSION; |
| fixedW = v.width * MAX_SURFACE_DIMENSION / v.height; |
| } |
| } |
| if (fixedW * fixedH > MAX_SURFACE_AREA) { |
| float area = MAX_SURFACE_AREA; |
| if (v.width > v.height) { |
| fixedW = (int)Math.sqrt(area * v.width / v.height); |
| fixedH = v.height * fixedW / v.width; |
| } else { |
| fixedH = (int)Math.sqrt(area * v.height / v.width); |
| fixedW = v.width * fixedH / v.height; |
| } |
| } |
| |
| if (fixedW != width || fixedH != height) { |
| // if we get here, either our dimensions or area (or both) |
| // exeeded our max, so we had to compute fixedW and fixedH |
| sView.getHolder().setFixedSize(fixedW, fixedH); |
| } else if (!sView.isFixedSize() && mZoomInProgress) { |
| // just freeze where we were (view size) until we're done with |
| // the zoom progress |
| sView.getHolder().setFixedSize(sView.getWidth(), |
| sView.getHeight()); |
| } else if (sView.isFixedSize() && !mZoomInProgress) { |
| /* The changing of visibility is a hack to get around a bug in |
| * the framework that causes the surface to revert to the size |
| * it was prior to being fixed before it redraws using the |
| * values currently in its layout. |
| * |
| * The surface is destroyed when it is set to invisible and then |
| * recreated at the new dimensions when it is made visible. The |
| * same destroy/create step occurs without the change in |
| * visibility, but then exhibits the behavior described in the |
| * previous paragraph. |
| */ |
| if (sView.getVisibility() == View.VISIBLE) { |
| sView.setVisibility(View.INVISIBLE); |
| sView.getHolder().setSizeFromLayout(); |
| // setLayoutParams() only requests the layout. If we set it |
| // to VISIBLE now, it will use the old dimension to set the |
| // size. Post a message to ensure that it shows the new size. |
| mWebView.mPrivateHandler.post(new Runnable() { |
| public void run() { |
| sView.setVisibility(View.VISIBLE); |
| } |
| }); |
| } else { |
| sView.getHolder().setSizeFromLayout(); |
| } |
| } |
| } |
| } |
| |
| void startZoom() { |
| mZoomInProgress = true; |
| for (ChildView v : mChildren) { |
| requestLayout(v); |
| } |
| } |
| |
| void endZoom() { |
| mZoomInProgress = false; |
| for (ChildView v : mChildren) { |
| requestLayout(v); |
| } |
| } |
| |
| void scaleAll() { |
| for (ChildView v : mChildren) { |
| requestLayout(v); |
| } |
| } |
| |
| void hideAll() { |
| if (mHidden) { |
| return; |
| } |
| for (ChildView v : mChildren) { |
| v.mView.setVisibility(View.GONE); |
| } |
| mHidden = true; |
| } |
| |
| void showAll() { |
| if (!mHidden) { |
| return; |
| } |
| for (ChildView v : mChildren) { |
| v.mView.setVisibility(View.VISIBLE); |
| } |
| mHidden = false; |
| } |
| |
| void postResetStateAll() { |
| mWebView.mPrivateHandler.post(new Runnable() { |
| public void run() { |
| mReadyToDraw = false; |
| } |
| }); |
| } |
| |
| void postReadyToDrawAll() { |
| mWebView.mPrivateHandler.post(new Runnable() { |
| public void run() { |
| mReadyToDraw = true; |
| for (ChildView v : mChildren) { |
| v.mView.setVisibility(View.VISIBLE); |
| } |
| } |
| }); |
| } |
| |
| ChildView hitTest(int contentX, int contentY) { |
| if (mHidden) { |
| return null; |
| } |
| for (ChildView v : mChildren) { |
| if (v.mView.getVisibility() == View.VISIBLE) { |
| if (contentX >= v.x && contentX < (v.x + v.width) |
| && contentY >= v.y && contentY < (v.y + v.height)) { |
| return v; |
| } |
| } |
| } |
| return null; |
| } |
| } |