ConversationWebView.java revision 916f819b1dfde084b8bd7941fd6f6d70d564e3e9
1/* 2 * Copyright (C) 2012 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package com.android.mail.browse; 19 20import android.content.Context; 21import android.content.res.Resources; 22import android.graphics.Bitmap; 23import android.graphics.Canvas; 24import android.graphics.Matrix; 25import android.graphics.Paint; 26import android.os.Handler; 27import android.util.AttributeSet; 28import android.view.Display; 29import android.view.MotionEvent; 30import android.view.WindowManager; 31import android.webkit.WebView; 32 33import com.android.mail.R; 34import com.android.mail.utils.LogTag; 35import com.android.mail.utils.LogUtils; 36 37import java.util.Set; 38import java.util.concurrent.CopyOnWriteArraySet; 39 40public class ConversationWebView extends WebView implements ScrollNotifier { 41 /** The initial delay when rendering in hardware layer. */ 42 private static int sWebviewInitialDelay; 43 44 /** The dimension of the offscreen bitmap used for software layer. */ 45 private static int sBitmapWidth; 46 private static int sBitmapHeight; 47 48 /** The default {@link Matrix} and {@link Paint} used to draw software layer. */ 49 private static Matrix sMatrix; 50 private static Paint sPaint; 51 52 private final Handler mHandler = new Handler(); 53 54 private Bitmap mBitmap; 55 private Canvas mCanvas; 56 57 private boolean mUseSoftwareLayer; 58 59 /** The visible portion of webview. */ 60 private int mLeft = 0; 61 private int mTop = 0; 62 63 /** {@link Runnable} to be run when the page is rendered in hardware layer. */ 64 private final Runnable mNotifyPageRenderedInHardwareLayer = new Runnable() { 65 @Override 66 public void run() { 67 // Switch to hardware layer. 68 mUseSoftwareLayer = false; 69 destroyBitmap(); 70 invalidate(); 71 } 72 }; 73 74 @Override 75 public void onDraw(Canvas canvas) { 76 // Always render in hardware layer to avoid flicker when switch. 77 super.onDraw(canvas); 78 79 // Render in software layer on top if needed. 80 if (mUseSoftwareLayer && mBitmap != null) { 81 mCanvas.save(); 82 mCanvas.translate(-mLeft, -mTop); 83 super.onDraw(mCanvas); 84 mCanvas.restore(); 85 canvas.save(); 86 canvas.translate(mLeft, mTop); 87 canvas.drawBitmap(mBitmap, sMatrix, sPaint); 88 canvas.restore(); 89 } 90 } 91 92 @Override 93 public void destroy() { 94 destroyBitmap(); 95 mHandler.removeCallbacksAndMessages(null); 96 97 super.destroy(); 98 } 99 100 /** 101 * Destroys the {@link Bitmap} used for software layer. 102 */ 103 private void destroyBitmap() { 104 if (mBitmap != null) { 105 mBitmap = null; 106 } 107 } 108 109 /** 110 * Enable this WebView to also draw to an internal software canvas until 111 * {@link #onVisibilityChanged(boolean)} is called. The software draw will happen every time 112 * a normal {@link #onDraw(Canvas)} happens, and will overwrite whatever is normally drawn 113 * (i.e. drawn in hardware) with the results of software rendering. 114 * <p> 115 * This is useful when you know that the WebView draws sooner to a software layer than it does 116 * to its normal hardware layer. 117 */ 118 public void setUseSoftwareLayer(boolean useSoftware) { 119 mUseSoftwareLayer = useSoftware; 120 if (useSoftware) { 121 if (sMatrix == null) { 122 final Resources res = getContext().getResources(); 123 sWebviewInitialDelay = res.getInteger(R.integer.webview_initial_delay); 124 125 final Display display = ((WindowManager) getContext().getSystemService( 126 Context.WINDOW_SERVICE)).getDefaultDisplay(); 127 sBitmapWidth = display.getWidth(); 128 sBitmapHeight = display.getHeight(); 129 130 sMatrix = new Matrix(); 131 sPaint = new Paint(); 132 } 133 134 // Create an offscreen bitmap. 135 mBitmap = Bitmap.createBitmap(sBitmapWidth, sBitmapHeight, Bitmap.Config.RGB_565); 136 mCanvas = new Canvas(mBitmap); 137 } 138 } 139 140 /** 141 * Notifies the {@link CustomWebView} that it has become visible. It can use this signal to 142 * switch between software and hardware layer. 143 */ 144 public void onVisibilityChanged(boolean isVisible) { 145 if (isVisible && mUseSoftwareLayer) { 146 // Schedule to switch from software layer to hardware layer in 1s. 147 mHandler.postDelayed(mNotifyPageRenderedInHardwareLayer, sWebviewInitialDelay); 148 } 149 } 150 151 // NARROW_COLUMNS reflow can trigger the document to change size, so notify interested parties. 152 public interface ContentSizeChangeListener { 153 void onHeightChange(int h); 154 } 155 156 private ContentSizeChangeListener mSizeChangeListener; 157 158 private int mCachedContentHeight; 159 160 private final int mViewportWidth; 161 private final float mDensity; 162 163 private final Set<ScrollListener> mScrollListeners = 164 new CopyOnWriteArraySet<ScrollListener>(); 165 166 /** 167 * True when WebView is handling a touch-- in between POINTER_DOWN and 168 * POINTER_UP/POINTER_CANCEL. 169 */ 170 private boolean mHandlingTouch; 171 172 private static final String LOG_TAG = LogTag.getLogTag(); 173 174 public ConversationWebView(Context c) { 175 this(c, null); 176 } 177 178 public ConversationWebView(Context c, AttributeSet attrs) { 179 super(c, attrs); 180 181 mViewportWidth = getResources().getInteger(R.integer.conversation_webview_viewport_px); 182 mDensity = getResources().getDisplayMetrics().density; 183 } 184 185 @Override 186 public void addScrollListener(ScrollListener l) { 187 mScrollListeners.add(l); 188 } 189 190 @Override 191 public void removeScrollListener(ScrollListener l) { 192 mScrollListeners.remove(l); 193 } 194 195 public void setContentSizeChangeListener(ContentSizeChangeListener l) { 196 mSizeChangeListener = l; 197 } 198 199 200 @Override 201 public int computeVerticalScrollRange() { 202 return super.computeVerticalScrollRange(); 203 } 204 205 @Override 206 public int computeVerticalScrollOffset() { 207 return super.computeVerticalScrollOffset(); 208 } 209 210 @Override 211 public int computeVerticalScrollExtent() { 212 return super.computeVerticalScrollExtent(); 213 } 214 215 @Override 216 public int computeHorizontalScrollRange() { 217 return super.computeHorizontalScrollRange(); 218 } 219 220 @Override 221 public int computeHorizontalScrollOffset() { 222 return super.computeHorizontalScrollOffset(); 223 } 224 225 @Override 226 public int computeHorizontalScrollExtent() { 227 return super.computeHorizontalScrollExtent(); 228 } 229 230 @Override 231 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 232 super.onScrollChanged(l, t, oldl, oldt); 233 234 // Update the visible portion of webview. 235 mLeft = l; 236 mTop = t; 237 238 for (ScrollListener listener : mScrollListeners) { 239 listener.onNotifierScroll(l, t); 240 } 241 } 242 243 @Override 244 public void invalidate() { 245 super.invalidate(); 246 247 if (mSizeChangeListener != null) { 248 final int contentHeight = getContentHeight(); 249 if (contentHeight != mCachedContentHeight) { 250 mCachedContentHeight = contentHeight; 251 mSizeChangeListener.onHeightChange(contentHeight); 252 } 253 } 254 } 255 256 @Override 257 public boolean onTouchEvent(MotionEvent ev) { 258 final int action = ev.getActionMasked(); 259 260 switch (action) { 261 case MotionEvent.ACTION_DOWN: 262 mHandlingTouch = true; 263 break; 264 case MotionEvent.ACTION_POINTER_DOWN: 265 LogUtils.d(LOG_TAG, "WebView disabling intercepts: POINTER_DOWN"); 266 requestDisallowInterceptTouchEvent(true); 267 break; 268 case MotionEvent.ACTION_CANCEL: 269 case MotionEvent.ACTION_UP: 270 mHandlingTouch = false; 271 break; 272 } 273 274 return super.onTouchEvent(ev); 275 } 276 277 public boolean isHandlingTouch() { 278 return mHandlingTouch; 279 } 280 281 public int getViewportWidth() { 282 return mViewportWidth; 283 } 284 285 /** 286 * Similar to {@link #getScale()}, except that it returns the initially expected scale, as 287 * determined by the ratio of actual screen pixels to logical HTML pixels. 288 * <p>This assumes that we are able to control the logical HTML viewport with a meta-viewport 289 * tag. 290 */ 291 public float getInitialScale() { 292 // an HTML meta-viewport width of "device-width" and unspecified (medium) density means 293 // that the default scale is effectively the screen density. 294 return mDensity; 295 } 296 297 public int screenPxToWebPx(int screenPx) { 298 return (int) (screenPx / getInitialScale()); 299 } 300 301 public int webPxToScreenPx(int webPx) { 302 return (int) (webPx * getInitialScale()); 303 } 304 305 public float screenPxToWebPxError(int screenPx) { 306 return screenPx / getInitialScale() - screenPxToWebPx(screenPx); 307 } 308 309 public float webPxToScreenPxError(int webPx) { 310 return webPx * getInitialScale() - webPxToScreenPx(webPx); 311 } 312} 313