• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
18 package com.android.mail.browse;
19 
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.graphics.Bitmap;
23 import android.graphics.Canvas;
24 import android.util.AttributeSet;
25 import android.view.MotionEvent;
26 import android.view.ScaleGestureDetector;
27 import android.view.ScaleGestureDetector.OnScaleGestureListener;
28 
29 import com.android.mail.R;
30 import com.android.mail.utils.LogTag;
31 import com.android.mail.utils.LogUtils;
32 
33 import java.util.Set;
34 import java.util.concurrent.CopyOnWriteArraySet;
35 
36 public class ConversationWebView extends MailWebView implements ScrollNotifier {
37     /** The initial delay when rendering in hardware layer. */
38     private final int mWebviewInitialDelay;
39 
40     private Bitmap mBitmap;
41     private Canvas mCanvas;
42 
43     private boolean mUseSoftwareLayer;
44     /**
45      * Whether this view is user-visible; we don't bother doing supplemental software drawing
46      * if the view is off-screen.
47      */
48     private boolean mVisible;
49 
50     /** {@link Runnable} to be run when the page is rendered in hardware layer. */
51     private final Runnable mNotifyPageRenderedInHardwareLayer = new Runnable() {
52         @Override
53         public void run() {
54             // Switch to hardware layer.
55             mUseSoftwareLayer = false;
56             destroyBitmap();
57             invalidate();
58         }
59     };
60 
61     @Override
onDraw(Canvas canvas)62     public void onDraw(Canvas canvas) {
63         // Always render in hardware layer to avoid flicker when switch.
64         super.onDraw(canvas);
65 
66         // Render in software layer on top if needed, and we're visible (i.e. it's worthwhile to
67         // do all this)
68         if (mUseSoftwareLayer && mVisible && getWidth() > 0 && getHeight() > 0) {
69             if (mBitmap == null) {
70                 try {
71                     // Create an offscreen bitmap.
72                     mBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.RGB_565);
73                     mCanvas = new Canvas(mBitmap);
74                 } catch (OutOfMemoryError e) {
75                     // just give up
76                     mBitmap = null;
77                     mCanvas = null;
78                     mUseSoftwareLayer = false;
79                 }
80             }
81 
82             if (mBitmap != null) {
83                 final int x = getScrollX();
84                 final int y = getScrollY();
85 
86                 mCanvas.save();
87                 mCanvas.translate(-x, -y);
88                 super.onDraw(mCanvas);
89                 mCanvas.restore();
90 
91                 canvas.drawBitmap(mBitmap, x, y, null /* paint */);
92             }
93         }
94     }
95 
96     @Override
destroy()97     public void destroy() {
98         destroyBitmap();
99         removeCallbacks(mNotifyPageRenderedInHardwareLayer);
100 
101         super.destroy();
102     }
103 
104     /**
105      * Destroys the {@link Bitmap} used for software layer.
106      */
destroyBitmap()107     private void destroyBitmap() {
108         if (mBitmap != null) {
109             mBitmap = null;
110             mCanvas = null;
111         }
112     }
113 
114     /**
115      * Enable this WebView to also draw to an internal software canvas until
116      * {@link #onRenderComplete()} is called. The software draw will happen every time
117      * a normal {@link #onDraw(Canvas)} happens, and will overwrite whatever is normally drawn
118      * (i.e. drawn in hardware) with the results of software rendering.
119      * <p>
120      * This is useful when you know that the WebView draws sooner to a software layer than it does
121      * to its normal hardware layer.
122      */
setUseSoftwareLayer(boolean useSoftware)123     public void setUseSoftwareLayer(boolean useSoftware) {
124         mUseSoftwareLayer = useSoftware;
125     }
126 
127     /**
128      * Notifies the {@link ConversationWebView} that it has become visible. It can use this signal
129      * to switch between software and hardware layer.
130      */
onRenderComplete()131     public void onRenderComplete() {
132         if (mUseSoftwareLayer) {
133             // Schedule to switch from software layer to hardware layer in 1s.
134             postDelayed(mNotifyPageRenderedInHardwareLayer, mWebviewInitialDelay);
135         }
136     }
137 
onUserVisibilityChanged(boolean visible)138     public void onUserVisibilityChanged(boolean visible) {
139         mVisible = visible;
140     }
141 
142     private ScaleGestureDetector mScaleDetector;
143 
144     private final int mViewportWidth;
145     private final float mDensity;
146 
147     private final Set<ScrollListener> mScrollListeners =
148             new CopyOnWriteArraySet<ScrollListener>();
149 
150     /**
151      * True when WebView is handling a touch-- in between POINTER_DOWN and
152      * POINTER_UP/POINTER_CANCEL.
153      */
154     private boolean mHandlingTouch;
155     private boolean mIgnoringTouch;
156 
157     private static final String LOG_TAG = LogTag.getLogTag();
158 
ConversationWebView(Context c)159     public ConversationWebView(Context c) {
160         this(c, null);
161     }
162 
ConversationWebView(Context c, AttributeSet attrs)163     public ConversationWebView(Context c, AttributeSet attrs) {
164         super(c, attrs);
165 
166         final Resources r = getResources();
167         mViewportWidth = r.getInteger(R.integer.conversation_webview_viewport_px);
168         mWebviewInitialDelay = r.getInteger(R.integer.webview_initial_delay);
169         mDensity = r.getDisplayMetrics().density;
170     }
171 
172     @Override
addScrollListener(ScrollListener l)173     public void addScrollListener(ScrollListener l) {
174         mScrollListeners.add(l);
175     }
176 
177     @Override
removeScrollListener(ScrollListener l)178     public void removeScrollListener(ScrollListener l) {
179         mScrollListeners.remove(l);
180     }
181 
setOnScaleGestureListener(OnScaleGestureListener l)182     public void setOnScaleGestureListener(OnScaleGestureListener l) {
183         if (l == null) {
184             mScaleDetector = null;
185         } else {
186             mScaleDetector = new ScaleGestureDetector(getContext(), l);
187         }
188     }
189 
190     @Override
onScrollChanged(int l, int t, int oldl, int oldt)191     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
192         super.onScrollChanged(l, t, oldl, oldt);
193 
194         for (ScrollListener listener : mScrollListeners) {
195             listener.onNotifierScroll(l, t);
196         }
197     }
198 
199     @Override
onTouchEvent(MotionEvent ev)200     public boolean onTouchEvent(MotionEvent ev) {
201         final int action = ev.getActionMasked();
202 
203         switch (action) {
204             case MotionEvent.ACTION_DOWN:
205                 mHandlingTouch = true;
206                 break;
207             case MotionEvent.ACTION_POINTER_DOWN:
208                 LogUtils.d(LOG_TAG, "WebView disabling intercepts: POINTER_DOWN");
209                 requestDisallowInterceptTouchEvent(true);
210                 if (mScaleDetector != null) {
211                     mIgnoringTouch = true;
212                     final MotionEvent fakeCancel = MotionEvent.obtain(ev);
213                     fakeCancel.setAction(MotionEvent.ACTION_CANCEL);
214                     super.onTouchEvent(fakeCancel);
215                 }
216                 break;
217             case MotionEvent.ACTION_CANCEL:
218             case MotionEvent.ACTION_UP:
219                 mHandlingTouch = false;
220                 mIgnoringTouch = false;
221                 break;
222         }
223 
224         final boolean handled = mIgnoringTouch || super.onTouchEvent(ev);
225 
226         if (mScaleDetector != null) {
227             mScaleDetector.onTouchEvent(ev);
228         }
229 
230         return handled;
231     }
232 
isHandlingTouch()233     public boolean isHandlingTouch() {
234         return mHandlingTouch;
235     }
236 
getViewportWidth()237     public int getViewportWidth() {
238         return mViewportWidth;
239     }
240 
241     /**
242      * Similar to {@link #getScale()}, except that it returns the initially expected scale, as
243      * determined by the ratio of actual screen pixels to logical HTML pixels.
244      * <p>This assumes that we are able to control the logical HTML viewport with a meta-viewport
245      * tag.
246      */
getInitialScale()247     public float getInitialScale() {
248         // an HTML meta-viewport width of "device-width" and unspecified (medium) density means
249         // that the default scale is effectively the screen density.
250         return mDensity;
251     }
252 
screenPxToWebPx(int screenPx)253     public int screenPxToWebPx(int screenPx) {
254         return (int) (screenPx / getInitialScale());
255     }
256 
webPxToScreenPx(int webPx)257     public int webPxToScreenPx(int webPx) {
258         return (int) (webPx * getInitialScale());
259     }
260 
screenPxToWebPxError(int screenPx)261     public float screenPxToWebPxError(int screenPx) {
262         return screenPx / getInitialScale() - screenPxToWebPx(screenPx);
263     }
264 
webPxToScreenPxError(int webPx)265     public float webPxToScreenPxError(int webPx) {
266         return webPx * getInitialScale() - webPxToScreenPx(webPx);
267     }
268 
269 }
270