ConversationWebView.java revision afc9b365dc9199ee9b2a1e598b8f40b3c78b6d9f
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.recycle(); 106 mBitmap = null; 107 } 108 } 109 110 /** 111 * Enable this WebView to also draw to an internal software canvas until 112 * {@link #onVisibilityChanged(boolean)} is called. The software draw will happen every time 113 * a normal {@link #onDraw(Canvas)} happens, and will overwrite whatever is normally drawn 114 * (i.e. drawn in hardware) with the results of software rendering. 115 * <p> 116 * This is useful when you know that the WebView draws sooner to a software layer than it does 117 * to its normal hardware layer. 118 */ 119 public void setUseSoftwareLayer(boolean useSoftware) { 120 mUseSoftwareLayer = useSoftware; 121 } 122 123 @Override 124 public void onFinishInflate() { 125 if (sMatrix == null) { 126 final Resources res = getContext().getResources(); 127 sWebviewInitialDelay = res.getInteger(R.integer.webview_initial_delay); 128 129 final Display display = ((WindowManager) getContext() 130 .getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 131 sBitmapWidth = display.getWidth(); 132 sBitmapHeight = display.getHeight(); 133 134 sMatrix = new Matrix(); 135 sPaint = new Paint(); 136 } 137 138 // Create an offscreen bitmap. 139 mBitmap = Bitmap.createBitmap(sBitmapWidth, sBitmapHeight, Bitmap.Config.RGB_565); 140 mCanvas = new Canvas(mBitmap); 141 } 142 143 /** 144 * Notifies the {@link CustomWebView} that it has become visible. It can use this signal to 145 * switch between software and hardware layer. 146 */ 147 public void onVisibilityChanged(boolean isVisible) { 148 if (isVisible && mUseSoftwareLayer) { 149 // Schedule to switch from software layer to hardware layer in 1s. 150 mHandler.postDelayed(mNotifyPageRenderedInHardwareLayer, sWebviewInitialDelay); 151 } 152 } 153 154 // NARROW_COLUMNS reflow can trigger the document to change size, so notify interested parties. 155 public interface ContentSizeChangeListener { 156 void onHeightChange(int h); 157 } 158 159 private ContentSizeChangeListener mSizeChangeListener; 160 161 private int mCachedContentHeight; 162 163 private final int mViewportWidth; 164 private final float mDensity; 165 166 private final Set<ScrollListener> mScrollListeners = 167 new CopyOnWriteArraySet<ScrollListener>(); 168 169 /** 170 * True when WebView is handling a touch-- in between POINTER_DOWN and 171 * POINTER_UP/POINTER_CANCEL. 172 */ 173 private boolean mHandlingTouch; 174 175 private static final String LOG_TAG = LogTag.getLogTag(); 176 177 public ConversationWebView(Context c) { 178 this(c, null); 179 } 180 181 public ConversationWebView(Context c, AttributeSet attrs) { 182 super(c, attrs); 183 184 mViewportWidth = getResources().getInteger(R.integer.conversation_webview_viewport_px); 185 mDensity = getResources().getDisplayMetrics().density; 186 } 187 188 @Override 189 public void addScrollListener(ScrollListener l) { 190 mScrollListeners.add(l); 191 } 192 193 @Override 194 public void removeScrollListener(ScrollListener l) { 195 mScrollListeners.remove(l); 196 } 197 198 public void setContentSizeChangeListener(ContentSizeChangeListener l) { 199 mSizeChangeListener = l; 200 } 201 202 203 @Override 204 public int computeVerticalScrollRange() { 205 return super.computeVerticalScrollRange(); 206 } 207 208 @Override 209 public int computeVerticalScrollOffset() { 210 return super.computeVerticalScrollOffset(); 211 } 212 213 @Override 214 public int computeVerticalScrollExtent() { 215 return super.computeVerticalScrollExtent(); 216 } 217 218 @Override 219 public int computeHorizontalScrollRange() { 220 return super.computeHorizontalScrollRange(); 221 } 222 223 @Override 224 public int computeHorizontalScrollOffset() { 225 return super.computeHorizontalScrollOffset(); 226 } 227 228 @Override 229 public int computeHorizontalScrollExtent() { 230 return super.computeHorizontalScrollExtent(); 231 } 232 233 @Override 234 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 235 super.onScrollChanged(l, t, oldl, oldt); 236 237 // Update the visible portion of webview. 238 mLeft = l; 239 mTop = t; 240 241 for (ScrollListener listener : mScrollListeners) { 242 listener.onNotifierScroll(l, t); 243 } 244 } 245 246 @Override 247 public void invalidate() { 248 super.invalidate(); 249 250 if (mSizeChangeListener != null) { 251 final int contentHeight = getContentHeight(); 252 if (contentHeight != mCachedContentHeight) { 253 mCachedContentHeight = contentHeight; 254 mSizeChangeListener.onHeightChange(contentHeight); 255 } 256 } 257 } 258 259 @Override 260 public boolean onTouchEvent(MotionEvent ev) { 261 final int action = ev.getActionMasked(); 262 263 switch (action) { 264 case MotionEvent.ACTION_DOWN: 265 mHandlingTouch = true; 266 break; 267 case MotionEvent.ACTION_POINTER_DOWN: 268 LogUtils.d(LOG_TAG, "WebView disabling intercepts: POINTER_DOWN"); 269 requestDisallowInterceptTouchEvent(true); 270 break; 271 case MotionEvent.ACTION_CANCEL: 272 case MotionEvent.ACTION_UP: 273 mHandlingTouch = false; 274 break; 275 } 276 277 return super.onTouchEvent(ev); 278 } 279 280 public boolean isHandlingTouch() { 281 return mHandlingTouch; 282 } 283 284 public int getViewportWidth() { 285 return mViewportWidth; 286 } 287 288 /** 289 * Similar to {@link #getScale()}, except that it returns the initially expected scale, as 290 * determined by the ratio of actual screen pixels to logical HTML pixels. 291 * <p>This assumes that we are able to control the logical HTML viewport with a meta-viewport 292 * tag. 293 */ 294 public float getInitialScale() { 295 // an HTML meta-viewport width of "device-width" and unspecified (medium) density means 296 // that the default scale is effectively the screen density. 297 return mDensity; 298 } 299 300 public int screenPxToWebPx(int screenPx) { 301 return (int) (screenPx / getInitialScale()); 302 } 303 304 public int webPxToScreenPx(int webPx) { 305 return (int) (webPx * getInitialScale()); 306 } 307 308 public float screenPxToWebPxError(int screenPx) { 309 return screenPx / getInitialScale() - screenPxToWebPx(screenPx); 310 } 311 312 public float webPxToScreenPxError(int webPx) { 313 return webPx * getInitialScale() - webPxToScreenPx(webPx); 314 } 315} 316